├── .dockerignore
├── .github
└── workflows
│ └── dotnet.yml
├── .gitignore
├── CompileTimeMethodExecutionGenerator.Example
├── CompileTimeMethodExecutionGenerator.Example.csproj
└── Program.cs
├── CompileTimeMethodExecutionGenerator.Generator
├── CompileTimeMethodExecutionGenerator.Generator.csproj
└── Generator.cs
├── CompileTimeMethodExecutionGenerator.sln
├── Dockerfile
├── LICENSE
├── README.md
└── docker-compose.yml
/.dockerignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | **/bin/
4 | **/obj/
5 | Dockerfile
6 | docker-compose.yml
7 | README.md
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | name: dotnet
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v1
18 | with:
19 | dotnet-version: 5.0.100
20 | - name: Install dependencies
21 | run: dotnet restore
22 | - name: Build
23 | run: dotnet build --no-restore
24 | - name: Run ======================> check the logs here
25 | run: dotnet run --project ./CompileTimeMethodExecutionGenerator.Example/CompileTimeMethodExecutionGenerator.Example.csproj
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.vspscc
94 | *.vssscc
95 | .builds
96 | *.pidb
97 | *.svclog
98 | *.scc
99 |
100 | # Chutzpah Test files
101 | _Chutzpah*
102 |
103 | # Visual C++ cache files
104 | ipch/
105 | *.aps
106 | *.ncb
107 | *.opendb
108 | *.opensdf
109 | *.sdf
110 | *.cachefile
111 | *.VC.db
112 | *.VC.VC.opendb
113 |
114 | # Visual Studio profiler
115 | *.psess
116 | *.vsp
117 | *.vspx
118 | *.sap
119 |
120 | # Visual Studio Trace Files
121 | *.e2e
122 |
123 | # TFS 2012 Local Workspace
124 | $tf/
125 |
126 | # Guidance Automation Toolkit
127 | *.gpState
128 |
129 | # ReSharper is a .NET coding add-in
130 | _ReSharper*/
131 | *.[Rr]e[Ss]harper
132 | *.DotSettings.user
133 |
134 | # TeamCity is a build add-in
135 | _TeamCity*
136 |
137 | # DotCover is a Code Coverage Tool
138 | *.dotCover
139 |
140 | # AxoCover is a Code Coverage Tool
141 | .axoCover/*
142 | !.axoCover/settings.json
143 |
144 | # Coverlet is a free, cross platform Code Coverage Tool
145 | coverage*.json
146 | coverage*.xml
147 | coverage*.info
148 |
149 | # Visual Studio code coverage results
150 | *.coverage
151 | *.coveragexml
152 |
153 | # NCrunch
154 | _NCrunch_*
155 | .*crunch*.local.xml
156 | nCrunchTemp_*
157 |
158 | # MightyMoose
159 | *.mm.*
160 | AutoTest.Net/
161 |
162 | # Web workbench (sass)
163 | .sass-cache/
164 |
165 | # Installshield output folder
166 | [Ee]xpress/
167 |
168 | # DocProject is a documentation generator add-in
169 | DocProject/buildhelp/
170 | DocProject/Help/*.HxT
171 | DocProject/Help/*.HxC
172 | DocProject/Help/*.hhc
173 | DocProject/Help/*.hhk
174 | DocProject/Help/*.hhp
175 | DocProject/Help/Html2
176 | DocProject/Help/html
177 |
178 | # Click-Once directory
179 | publish/
180 |
181 | # Publish Web Output
182 | *.[Pp]ublish.xml
183 | *.azurePubxml
184 | # Note: Comment the next line if you want to checkin your web deploy settings,
185 | # but database connection strings (with potential passwords) will be unencrypted
186 | *.pubxml
187 | *.publishproj
188 |
189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
190 | # checkin your Azure Web App publish settings, but sensitive information contained
191 | # in these scripts will be unencrypted
192 | PublishScripts/
193 |
194 | # NuGet Packages
195 | *.nupkg
196 | # NuGet Symbol Packages
197 | *.snupkg
198 | # The packages folder can be ignored because of Package Restore
199 | **/[Pp]ackages/*
200 | # except build/, which is used as an MSBuild target.
201 | !**/[Pp]ackages/build/
202 | # Uncomment if necessary however generally it will be regenerated when needed
203 | #!**/[Pp]ackages/repositories.config
204 | # NuGet v3's project.json files produces more ignorable files
205 | *.nuget.props
206 | *.nuget.targets
207 |
208 | # Microsoft Azure Build Output
209 | csx/
210 | *.build.csdef
211 |
212 | # Microsoft Azure Emulator
213 | ecf/
214 | rcf/
215 |
216 | # Windows Store app package directories and files
217 | AppPackages/
218 | BundleArtifacts/
219 | Package.StoreAssociation.xml
220 | _pkginfo.txt
221 | *.appx
222 | *.appxbundle
223 | *.appxupload
224 |
225 | # Visual Studio cache files
226 | # files ending in .cache can be ignored
227 | *.[Cc]ache
228 | # but keep track of directories ending in .cache
229 | !?*.[Cc]ache/
230 |
231 | # Others
232 | ClientBin/
233 | ~$*
234 | *~
235 | *.dbmdl
236 | *.dbproj.schemaview
237 | *.jfm
238 | *.pfx
239 | *.publishsettings
240 | orleans.codegen.cs
241 |
242 | # Including strong name files can present a security risk
243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
244 | #*.snk
245 |
246 | # Since there are multiple workflows, uncomment next line to ignore bower_components
247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
248 | #bower_components/
249 |
250 | # RIA/Silverlight projects
251 | Generated_Code/
252 |
253 | # Backup & report files from converting an old project file
254 | # to a newer Visual Studio version. Backup files are not needed,
255 | # because we have git ;-)
256 | _UpgradeReport_Files/
257 | Backup*/
258 | UpgradeLog*.XML
259 | UpgradeLog*.htm
260 | ServiceFabricBackup/
261 | *.rptproj.bak
262 |
263 | # SQL Server files
264 | *.mdf
265 | *.ldf
266 | *.ndf
267 |
268 | # Business Intelligence projects
269 | *.rdl.data
270 | *.bim.layout
271 | *.bim_*.settings
272 | *.rptproj.rsuser
273 | *- [Bb]ackup.rdl
274 | *- [Bb]ackup ([0-9]).rdl
275 | *- [Bb]ackup ([0-9][0-9]).rdl
276 |
277 | # Microsoft Fakes
278 | FakesAssemblies/
279 |
280 | # GhostDoc plugin setting file
281 | *.GhostDoc.xml
282 |
283 | # Node.js Tools for Visual Studio
284 | .ntvs_analysis.dat
285 | node_modules/
286 |
287 | # Visual Studio 6 build log
288 | *.plg
289 |
290 | # Visual Studio 6 workspace options file
291 | *.opt
292 |
293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
294 | *.vbw
295 |
296 | # Visual Studio LightSwitch build output
297 | **/*.HTMLClient/GeneratedArtifacts
298 | **/*.DesktopClient/GeneratedArtifacts
299 | **/*.DesktopClient/ModelManifest.xml
300 | **/*.Server/GeneratedArtifacts
301 | **/*.Server/ModelManifest.xml
302 | _Pvt_Extensions
303 |
304 | # Paket dependency manager
305 | .paket/paket.exe
306 | paket-files/
307 |
308 | # FAKE - F# Make
309 | .fake/
310 |
311 | # CodeRush personal settings
312 | .cr/personal
313 |
314 | # Python Tools for Visual Studio (PTVS)
315 | __pycache__/
316 | *.pyc
317 |
318 | # Cake - Uncomment if you are using it
319 | # tools/**
320 | # !tools/packages.config
321 |
322 | # Tabs Studio
323 | *.tss
324 |
325 | # Telerik's JustMock configuration file
326 | *.jmconfig
327 |
328 | # BizTalk build output
329 | *.btp.cs
330 | *.btm.cs
331 | *.odx.cs
332 | *.xsd.cs
333 |
334 | # OpenCover UI analysis results
335 | OpenCover/
336 |
337 | # Azure Stream Analytics local run output
338 | ASALocalRun/
339 |
340 | # MSBuild Binary and Structured Log
341 | *.binlog
342 |
343 | # NVidia Nsight GPU debugger configuration file
344 | *.nvuser
345 |
346 | # MFractors (Xamarin productivity tool) working folder
347 | .mfractor/
348 |
349 | # Local History for Visual Studio
350 | .localhistory/
351 |
352 | # BeatPulse healthcheck temp database
353 | healthchecksdb
354 |
355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
356 | MigrationBackup/
357 |
358 | # Ionide (cross platform F# VS Code tools) working folder
359 | .ionide/
360 |
361 | # Fody - auto-generated XML schema
362 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/CompileTimeMethodExecutionGenerator.Example/CompileTimeMethodExecutionGenerator.Example.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 |
7 |
8 |
9 |
10 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/CompileTimeMethodExecutionGenerator.Example/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using CompileTimeMethodExecutionGenerator;
4 |
5 | namespace CompileTimeMethodExecutionGenerator.Example
6 | {
7 | ///
8 | /// The following class will be extended during compilation by the source generator "CompileTimeMethodExecutionGenerator".
9 | /// To allow for that to happen, it has to be made partial.
10 | ///
11 | public partial class Calculator
12 | {
13 | ///
14 | /// This method does something that is cpu intensive and should always have the same result; it calculates pi in 20000 digits.
15 | ///
16 | /// Of course, you could just run it once and place the resulting string in the method body to achieve the same result.
17 | /// But that would be more difficult to maintain.
18 | ///
19 | /// Also, there is System.Math.PI. You should obviously use that if you want to do something with pi.
20 | /// But that's not the point here; this is a proof of concept example.
21 | ///
22 | [CompileTimeExecutor]
23 | public string Pi() {
24 | // Code derived from: https://stackoverflow.com/a/11679007/4624255
25 | const int digits = 20000;
26 |
27 | uint[] x = new uint[digits*10/3+2];
28 | uint[] r = new uint[digits*10/3+2];
29 |
30 | uint[] pi = new uint[digits];
31 |
32 | for (int j = 0; j < x.Length; j++)
33 | x[j] = 20;
34 |
35 | for (int i = 0; i < digits; i++)
36 | {
37 | uint carry = 0;
38 | for (int j = 0; j < x.Length; j++)
39 | {
40 | uint num = (uint)(x.Length - j - 1);
41 | uint dem = num * 2 + 1;
42 |
43 | x[j] += carry;
44 |
45 | uint q = x[j] / dem;
46 | r[j] = x[j] % dem;
47 |
48 | carry = q * num;
49 | }
50 |
51 |
52 | pi[i] = (x[x.Length-1] / 10);
53 |
54 |
55 | r[x.Length - 1] = x[x.Length - 1] % 10; ;
56 |
57 | for (int j = 0; j < x.Length; j++)
58 | x[j] = r[j] * 10;
59 | }
60 |
61 | var result = "";
62 |
63 | uint c = 0;
64 |
65 | for(int i = pi.Length - 1; i >=0; i--)
66 | {
67 | pi[i] += c;
68 | c = pi[i] / 10;
69 |
70 | result = (pi[i] % 10).ToString() + result;
71 | }
72 |
73 | return result;
74 | }
75 | }
76 |
77 | class Program
78 | {
79 | static void Main(string[] args)
80 | {
81 | var calculator = new Calculator();
82 |
83 | // Execute the method like you normally would first and measure the time it takes
84 | Stopwatch stopWatch = new Stopwatch();
85 | stopWatch.Start();
86 | Console.WriteLine($"Pi calculated with {calculator.Pi().Length} digits");
87 | stopWatch.Stop();
88 | Console.WriteLine($"Execution took {stopWatch.Elapsed.TotalMilliseconds}ms");
89 |
90 | // Now execute the compile time generated version of the same method
91 | stopWatch.Reset();
92 | stopWatch.Start();
93 | Console.WriteLine($"Pi calculated with {calculator.PiCompileTime().Length} digits (but performed calculation during compilation)");
94 | stopWatch.Stop();
95 | Console.WriteLine($"Execution took {stopWatch.Elapsed.TotalMilliseconds}ms");
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/CompileTimeMethodExecutionGenerator.Generator/CompileTimeMethodExecutionGenerator.Generator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 8.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/CompileTimeMethodExecutionGenerator.Generator/Generator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CSharp;
7 | using Microsoft.CodeAnalysis.CSharp.Syntax;
8 | using Microsoft.CodeAnalysis.Text;
9 | using Microsoft.CodeAnalysis.Emit;
10 | using System.IO;
11 | using System.Reflection;
12 |
13 | namespace CompileTimeMethodExecutionGenerator.Generator
14 | {
15 | [Generator]
16 | public class Generator : ISourceGenerator
17 | {
18 | // The attribute that allows decorating methods with [CompileTimeExecutor] can be added to the compilation
19 | private const string attributeText = @"
20 | using System;
21 | namespace CompileTimeMethodExecutionGenerator
22 | {
23 | [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
24 | sealed class CompileTimeExecutorAttribute : Attribute
25 | {
26 | public CompileTimeExecutorAttribute()
27 | {
28 | }
29 | }
30 | }";
31 |
32 | public void Execute(GeneratorExecutionContext context)
33 | {
34 | // Always add the attribute itself to the compilation
35 | context.AddSource("CompileTimeExecutorAttribute", SourceText.From(attributeText, Encoding.UTF8));
36 |
37 | // retreive the populated receiver
38 | if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
39 | return;
40 |
41 | // we're going to create a new compilation that contains the attribute.
42 | // TODO: we should allow source generators to provide source during initialize, so that this step isn't required.
43 | CSharpParseOptions options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions;
44 | Compilation compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options));
45 |
46 | // get the newly bound attribute, and INotifyPropertyChanged
47 | INamedTypeSymbol attributeSymbol = compilation.GetTypeByMetadataName("CompileTimeMethodExecutionGenerator.CompileTimeExecutorAttribute");
48 |
49 | foreach (MethodDeclarationSyntax method in receiver.CandidateMethods)
50 | {
51 | SemanticModel model = compilation.GetSemanticModel(method.SyntaxTree);
52 |
53 | var methodSymbol = model.GetDeclaredSymbol(method) as IMethodSymbol;
54 |
55 | foreach (AttributeSyntax attribute in method.AttributeLists
56 | .SelectMany(al => al.Attributes)
57 | .Where(at => string.Equals("CompileTimeExecutor", at.Name.ToString())))
58 | {
59 | string calculatedResult = CalculateResult(method, methodSymbol);
60 |
61 | context.AddSource($"{methodSymbol.ContainingNamespace.ToDisplayString()}_{methodSymbol.ContainingType.Name}_{method.Identifier.Text}.gen.cs",
62 | SourceText.From($@"namespace {methodSymbol.ContainingNamespace.ToDisplayString()}
63 | {{
64 | public partial class {methodSymbol.ContainingType.Name}
65 | {{
66 | public string {method.Identifier.Text}CompileTime()
67 | {{
68 | return ""{calculatedResult}"";
69 | }}
70 | }}
71 | }}", Encoding.UTF8));
72 | }
73 | }
74 | }
75 |
76 | private static string CalculateResult(MethodDeclarationSyntax method, IMethodSymbol methodSymbol)
77 | {
78 | string assemblyName = Path.GetRandomFileName();
79 | MetadataReference[] references = new MetadataReference[]
80 | {
81 | MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
82 | MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
83 | };
84 |
85 | CSharpParseOptions options = method.SyntaxTree.Options as CSharpParseOptions;
86 |
87 | CSharpCompilation compilation = CSharpCompilation.Create(
88 | assemblyName,
89 | syntaxTrees: new[] { CSharpSyntaxTree.ParseText(
90 | SourceText.From(
91 | $"public class C {{ public {method.ReturnType.ToString()} M() {method.Body.ToFullString()} }}", Encoding.UTF8),
92 | options) },
93 | references: references,
94 | options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
95 |
96 | try
97 | {
98 | using (var ms = new MemoryStream())
99 | {
100 | EmitResult result = compilation.Emit(ms);
101 |
102 | if (!result.Success)
103 | {
104 | IEnumerable failures = result.Diagnostics.Where(diagnostic =>
105 | diagnostic.IsWarningAsError ||
106 | diagnostic.Severity == DiagnosticSeverity.Error);
107 |
108 | throw new Exception(string.Join("\r\n", failures.Select(f => $"{f.Id} {f.GetMessage()}")));
109 | }
110 | else
111 | {
112 | ms.Seek(0, SeekOrigin.Begin);
113 | Assembly assembly = Assembly.Load(ms.ToArray());
114 |
115 | Type type = assembly.GetType("C");
116 | object obj = Activator.CreateInstance(type);
117 | var res = type.InvokeMember("M",
118 | BindingFlags.Default | BindingFlags.InvokeMethod,
119 | null,
120 | obj,
121 | new object[0])?.ToString();
122 |
123 | return res;
124 | }
125 | }
126 | }
127 | catch(Exception ex)
128 | {
129 | return $"Exception when executing method for injecting into compilation {ex.Message}";
130 | }
131 | }
132 |
133 | public void Initialize(GeneratorInitializationContext context)
134 | {
135 | // Register a syntax receiver that will be created for each generation pass
136 | context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
137 | }
138 |
139 | ///
140 | /// Created on demand before each generation pass
141 | ///
142 | class SyntaxReceiver : ISyntaxReceiver
143 | {
144 | public List CandidateMethods { get; } = new List();
145 |
146 | ///
147 | /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
148 | ///
149 | public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
150 | {
151 | // any method with at least one attribute is a candidate
152 | if (syntaxNode is MethodDeclarationSyntax methodDeclarationSyntax
153 | && methodDeclarationSyntax.AttributeLists.Count > 0)
154 | {
155 | CandidateMethods.Add(methodDeclarationSyntax);
156 | }
157 | }
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/CompileTimeMethodExecutionGenerator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileTimeMethodExecutionGenerator.Generator", "CompileTimeMethodExecutionGenerator.Generator\CompileTimeMethodExecutionGenerator.Generator.csproj", "{1C805869-A87F-4F87-81E6-72B81E752F1C}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileTimeMethodExecutionGenerator.Example", "CompileTimeMethodExecutionGenerator.Example\CompileTimeMethodExecutionGenerator.Example.csproj", "{FDF39D05-995E-47C3-B100-84EAA1DD53F5}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Debug|x64.Build.0 = Debug|Any CPU
27 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Debug|x86.ActiveCfg = Debug|Any CPU
28 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Debug|x86.Build.0 = Debug|Any CPU
29 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Release|x64.ActiveCfg = Release|Any CPU
32 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Release|x64.Build.0 = Release|Any CPU
33 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Release|x86.ActiveCfg = Release|Any CPU
34 | {1C805869-A87F-4F87-81E6-72B81E752F1C}.Release|x86.Build.0 = Release|Any CPU
35 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Debug|x64.ActiveCfg = Debug|Any CPU
38 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Debug|x64.Build.0 = Debug|Any CPU
39 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Debug|x86.ActiveCfg = Debug|Any CPU
40 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Debug|x86.Build.0 = Debug|Any CPU
41 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Release|x64.ActiveCfg = Release|Any CPU
44 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Release|x64.Build.0 = Release|Any CPU
45 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Release|x86.ActiveCfg = Release|Any CPU
46 | {FDF39D05-995E-47C3-B100-84EAA1DD53F5}.Release|x86.Build.0 = Release|Any CPU
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:5.0
2 |
3 | WORKDIR /app/CompileTimeMethodExecutionGenerator.Generator
4 | COPY ./CompileTimeMethodExecutionGenerator.Generator/CompileTimeMethodExecutionGenerator.Generator.csproj ./
5 | RUN dotnet restore
6 |
7 | WORKDIR /app/CompileTimeMethodExecutionGenerator.Example
8 | COPY ./CompileTimeMethodExecutionGenerator.Example/CompileTimeMethodExecutionGenerator.Example.csproj ./
9 | RUN dotnet restore
10 |
11 | WORKDIR /app
12 |
13 | COPY . ./
14 |
15 | RUN dotnet build ./CompileTimeMethodExecutionGenerator.Example/CompileTimeMethodExecutionGenerator.Example.csproj --no-restore
16 |
17 | CMD dotnet run --project ./CompileTimeMethodExecutionGenerator.Example/CompileTimeMethodExecutionGenerator.Example.csproj
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Robin Hermanussen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Compile Time Method Execution Generator
4 |
5 | A ".NET 5" source generator proof of concept that allows executing a method during compilation, so that it can be really fast during runtime.
6 |
7 | ## What does it do?
8 |
9 | [This blogpost](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/) describes how C# source generators work. In short, C# source generators allow code to be generated and added to the compilation while the compilation is running.
10 |
11 | This particular generator looks for a method that is decorated with the `[CompileTimeExecutor]` attribute, and then adds the same method with a "CompileTime" postfix in its name to the same class. This method returns the result immediately without having to execute the logic.
12 |
13 | This is possible, because the source generator compiles and executes the original method and then has the new "CompileTime" version of the method return the result immediately.
14 |
15 | ## But why?
16 |
17 | I'm not sure if there are that many real world use cases, but please let me know if you have one ([Send me a Tweet](https://twitter.com/knifecore/)).
18 |
19 | I just thought it would be fun. And potentially, it could be useful if you have a method that:
20 | - Always returns the same result
21 | - The implementation is slow
22 | - You may want to change the implementation of the method during development
23 | - You don't mind if this slightly slows down your project's compilation
24 |
25 | It may actually be a really bad idea to use this. I could imagine that visual studio will be very slow because of this (because the live compilation will also be very slow).
26 |
27 | ## How does it work?
28 |
29 | Well, take a look for yourself! The "CompileTimeMethodExecutionGenerator.Example" shows how you could use the generator. A method that calculates pi in 20000 digits is decorated with the "CompileTime" attribute. The program benchmarks running the method during runtime as well as compile time.
30 |
31 | If you want to understand the inner workings, then take a look at the "CompileTimeMethodExecutionGenerator.Generator" project, which contains the actual generator.
32 |
33 | ## How can I see the result?
34 |
35 | Just take a look at the output of [the latest run here](https://github.com/hermanussen/CompileTimeMethodExecutionGenerator/actions?query=workflow%3Adotnet). In the build log, expand the "Run" section and you'll find something like this:
36 | ```
37 | Pi calculated with 20000 digits
38 | Execution took 26482.0309ms
39 | Pi calculated with 20000 digits (but performed calculation during compilation)
40 | Execution took 0.2029ms
41 | ```
42 |
43 | ## How can I run it myself?
44 |
45 | I've rolled the compilation and running of the example in a Docker that is included here. The easiest way to run it is by running `docker-compose up --build` and looking at the output. Example output:
46 | ```
47 | Attaching to compiletimemethodexecutiongenerator_cg_1
48 | cg_1 | Pi calculated with 20000 digits
49 | cg_1 | Execution took 22455.9488ms
50 | cg_1 | Pi calculated with 20000 digits (but performed calculation during compilation)
51 | cg_1 | Execution took 0.1939ms
52 | compiletimemethodexecutiongenerator_cg_1 exited with code 0
53 | ```
54 |
55 | As you can see, there are significant performance gains possible. Even though this is a very contrived example, obviously.
56 |
57 | ## Is there a future for this?
58 |
59 | I don't know. It's just something that I thought was interesting. There are many limitations at this point, that could be addressed:
60 | - I've only tested with `string` and `int` as the return type. Potentially, the generator could support any serializable type.
61 | - The methods can not have any parameters at this time. The parameter values should be known at compile time for this to work. Maybe adding the values to the attribute or looking at the syntax trees could be used to determine these values.
62 | - There's probably a million ways in which using this could go wrong at this point. I should add some unit tests and handle many of the edge cases.
63 | - ... send me your ideas and opinions ([Send me a Tweet](https://twitter.com/knifecore/))!
64 |
65 | I may invest some time in this if there are more people interested.
66 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | cg:
4 | build: .
--------------------------------------------------------------------------------