├── .gitignore
├── Directory.Build.props
├── ExpressionDebugger.Console
├── ExpressionDebugger.Console.csproj
└── Program.cs
├── ExpressionDebugger.Tests
├── DebugInfoInjectorTest.cs
└── ExpressionDebugger.Tests.csproj
├── ExpressionDebugger.sln
├── ExpressionDebugger
├── ExpressionCompilationOptions.cs
├── ExpressionCompiler.cs
├── ExpressionDebugger.csproj
├── ExpressionDebugger.snk
└── ExpressionDebuggerExtensions.cs
├── ExpressionTranslator
├── ExpressionDefinitions.cs
├── ExpressionTranslator.cs
├── ExpressionTranslator.csproj
├── ExpressionTranslator.snk
├── ExpressionTranslatorExtensions.cs
├── Extensions.cs
├── PropertyDefinitions.cs
└── TypeDefinitions.cs
├── LICENSE
├── Pack.bat
├── Publish.bat
├── README.md
└── icon.png
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
6 |
7 |
8 | chaowlert;andrerav
9 | Copyright (c) $([System.DateTime]::Now.ToString(`yyyy`)) Chaowlert Chaisrichalermpol, Andreas Ravnestad
10 | false
11 | https://github.com/MapsterMapper/ExpressionDebugger
12 | https://github.com/MapsterMapper/ExpressionDebugger
13 | ./icon.png
14 | https://cloud.githubusercontent.com/assets/5763993/26522656/41e28a6e-432f-11e7-9cae-7856f927d1a1.png
15 | MIT
16 | expression;linq;debug
17 | 10
18 |
19 |
--------------------------------------------------------------------------------
/ExpressionDebugger.Console/ExpressionDebugger.Console.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net461
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ExpressionDebugger.Console/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 |
4 | namespace ExpressionDebugger.Console
5 | {
6 | class Program
7 | {
8 | static void Main(string[] args)
9 | {
10 | var p1 = Expression.Parameter(typeof(int));
11 | var p2 = Expression.Parameter(typeof(int));
12 | var body = Expression.Add(p1, Expression.Block(
13 | new Expression[] {
14 | Expression.Call(typeof(System.Console).GetMethod("WriteLine", new [] { typeof(int) }), p2),
15 | p2,
16 | }));
17 | var lambda = Expression.Lambda>(body, p1, p2);
18 | var fun = lambda.CompileWithDebugInfo();
19 | var result = fun(1, 2);
20 | System.Console.WriteLine(result);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ExpressionDebugger.Tests/DebugInfoInjectorTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 | using System.Reflection;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace ExpressionDebugger.Tests
9 | {
10 | [TestClass]
11 | public class DebugInfoInjectorTest
12 | {
13 | [TestMethod]
14 | public void TestBinary()
15 | {
16 | Expression> fn = (a, b) => a + b;
17 | var str = fn.ToScript();
18 | Assert.AreEqual(@"
19 | public int Main(int a, int b)
20 | {
21 | return a + b;
22 | }".Trim()
23 | , str);
24 | }
25 |
26 | [TestMethod]
27 | public void TestBinary_PowerAssign()
28 | {
29 | var exp = Expression.PowerAssign(Expression.Variable(typeof(double), "d"), Expression.Constant(2d));
30 | var str = exp.ToScript();
31 | Assert.AreEqual("d = Math.Pow(d, 2d)", str);
32 | }
33 |
34 | [TestMethod]
35 | public void TestBinary_ArrayIndex()
36 | {
37 | Expression> fn = a => a[0];
38 | var str = fn.ToScript();
39 | Assert.AreEqual(@"
40 | public int Main(int[] a)
41 | {
42 | return a[0];
43 | }".Trim()
44 | , str);
45 | }
46 |
47 | [TestMethod]
48 | public void TestBlock()
49 | {
50 | var p1 = Expression.Variable(typeof(int));
51 | var block = Expression.Block(new[] { p1 }, Expression.Add(p1, p1));
52 | var str = block.ToScript();
53 | Assert.AreEqual(@"
54 | int p1;
55 |
56 | p1 + p1;".Trim(), str);
57 | }
58 |
59 | [TestMethod]
60 | public void Test_Conditional()
61 | {
62 | Expression> fn = a => a < 0 ? a - 1 : a + 1;
63 | var str = fn.ToScript();
64 | Assert.AreEqual(@"
65 | public int Main(int a)
66 | {
67 | return a < 0 ? a - 1 : a + 1;
68 | }".Trim()
69 | , str);
70 | }
71 |
72 | [TestMethod]
73 | public void TestConditional_Block()
74 | {
75 | var exp = Expression.Condition(
76 | Expression.Equal(Expression.Constant(1), Expression.Constant(2)),
77 | Expression.Constant(4),
78 | Expression.Constant(3),
79 | typeof(void));
80 | var str = exp.ToScript();
81 | Assert.AreEqual(@"
82 | if (1 == 2)
83 | {
84 | 4;
85 | }
86 | else
87 | {
88 | 3;
89 | }".Trim()
90 | , str);
91 | }
92 |
93 | [TestMethod]
94 | public void TestConditional_Block_Chain()
95 | {
96 | var exp = Expression.Condition(
97 | Expression.Equal(Expression.Constant(1), Expression.Constant(2)),
98 | Expression.Constant(4),
99 | Expression.Condition(
100 | Expression.Equal(Expression.Constant(5), Expression.Constant(6)),
101 | Expression.Constant(3),
102 | Expression.Constant(2),
103 | typeof(void)),
104 | typeof(void));
105 | var str = exp.ToScript();
106 | Assert.AreEqual(@"
107 | if (1 == 2)
108 | {
109 | 4;
110 | }
111 | else if (5 == 6)
112 | {
113 | 3;
114 | }
115 | else
116 | {
117 | 2;
118 | }".Trim()
119 | , str);
120 | }
121 |
122 | [TestMethod]
123 | public void TestConstants()
124 | {
125 | Expression> fn = s => s == "x" || s == @"\" || s == null || s.IsNormalized() == false || s.GetType() == typeof(string) ? 'x' : s[0];
126 | var str = fn.ToScript();
127 | Assert.AreEqual(@"
128 | public char Main(string s)
129 | {
130 | return s == ""x"" || s == @""\"" || s == null || s.IsNormalized() == false || s.GetType() == typeof(string) ? 'x' : s[0];
131 | }".Trim()
132 | , str);
133 |
134 | Expression> fn2 = () => 1f.ToString() + 2m.ToString() + ((byte)1).ToString() + DayOfWeek.Friday.ToString() + default(DateTime).ToString();
135 | var str2 = fn2.ToScript();
136 | Assert.AreEqual(@"
137 | public string Main()
138 | {
139 | return 1f.ToString() + 2m.ToString() + ((byte)1).ToString() + DayOfWeek.Friday.ToString() + default(DateTime).ToString();
140 | }".Trim()
141 | , str2);
142 | }
143 |
144 | [TestMethod]
145 | public void TestConstant_DateTime()
146 | {
147 | var now = DateTime.Now;
148 | var expr = Expression.Constant(now);
149 | var script = expr.ToScript();
150 | Assert.AreEqual(@"
151 | private DateTime DateTime1;
152 | DateTime1".Trim(), script);
153 | }
154 |
155 | // [TestMethod]
156 | // public void TestDynamic()
157 | // {
158 | // var dynType = typeof(ExpandoObject);
159 | // var p1 = Expression.Variable(dynType);
160 | // var line1 = Expression.Dynamic(Binder.Convert(CSharpBinderFlags.None, typeof(Poco), dynType), typeof(object), p1);
161 | // var line2 = Expression.Dynamic(Binder.GetMember(CSharpBinderFlags.None, "Blah", dynType,
162 | // new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }), typeof(object), p1);
163 | // var line3 = Expression.Dynamic(Binder.SetMember(CSharpBinderFlags.None, "Blah", dynType,
164 | // new[]
165 | // {
166 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
167 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
168 | // }), typeof(object), p1, Expression.Constant(0));
169 | // var line4 = Expression.Dynamic(Binder.GetIndex(CSharpBinderFlags.None, dynType,
170 | // new[]
171 | // {
172 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
173 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
174 | // }), typeof(object), p1, Expression.Constant(1));
175 | // var line5 = Expression.Dynamic(Binder.SetIndex(CSharpBinderFlags.None, dynType,
176 | // new[]
177 | // {
178 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
179 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
180 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
181 | // }), typeof(object), p1, Expression.Constant(1), Expression.Constant(0));
182 | // var line6 = Expression.Dynamic(Binder.InvokeMember(CSharpBinderFlags.None, "Blah", null, dynType,
183 | // new[]
184 | // {
185 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
186 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
187 | // }), typeof(object), p1, Expression.Constant(2));
188 | // var line7 = Expression.Dynamic(Binder.Invoke(CSharpBinderFlags.None, dynType,
189 | // new[]
190 | // {
191 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
192 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
193 | // }), typeof(object), p1, Expression.Constant(2));
194 | // var line8 = Expression.Dynamic(Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.Negate, dynType,
195 | // new[]
196 | // {
197 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
198 | // }), typeof(object), p1);
199 | // var line9 = Expression.Dynamic(Binder.BinaryOperation(CSharpBinderFlags.None, ExpressionType.Add, dynType,
200 | // new[]
201 | // {
202 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
203 | // CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
204 | // }), typeof(object), p1, Expression.Constant(3));
205 | // var expr = Expression.Block(new[] { p1 }, line1, line2, line3, line4, line5, line6, line7, line8, line9);
206 | // var str = expr.ToScript();
207 | // Assert.AreEqual(@"
208 | //dynamic p1;
209 |
210 | //(Poco)p1;
211 | //p1.Blah;
212 | //p1.Blah = 0;
213 | //p1[1];
214 | //p1[1] = 0;
215 | //p1.Blah(2);
216 | //p1(2);
217 | //-p1;
218 | //p1 + 3;"
219 | // , str);
220 | // }
221 |
222 | [TestMethod]
223 | public void TestDefault()
224 | {
225 | var exp = Expression.Default(typeof(int));
226 | var str = exp.ToScript();
227 | Assert.AreEqual("default(int)", str);
228 | }
229 |
230 | [TestMethod]
231 | public void TestGroup()
232 | {
233 | Expression> fn = x => -(-x) + 1 + x - (1 - x);
234 | var str = fn.ToScript();
235 | Assert.AreEqual(@"
236 | public int Main(int x)
237 | {
238 | return -(-x) + 1 + x - (1 - x);
239 | }".Trim()
240 | , str);
241 | }
242 |
243 | [TestMethod]
244 | public void TestGroup_MultiLine()
245 | {
246 | var p = Expression.Variable(typeof(int), "p");
247 | var exp = Expression.Add(
248 | p,
249 | Expression.Block(
250 | new Expression[] {
251 | Expression.Call(typeof(Console).GetMethod(nameof(Console.WriteLine), new [] { typeof(int) }), p),
252 | p,
253 | }
254 | ));
255 | var str = exp.ToScript();
256 | Assert.AreEqual(@"p + (new Func(() => {
257 | Console.WriteLine(p);
258 | return p;
259 | }))()"
260 | , str);
261 | }
262 |
263 | [TestMethod]
264 | public void TestIndex()
265 | {
266 | var p1 = Expression.Parameter(typeof(int[]));
267 | var expr = Expression.MakeIndex(p1, null, new[] { Expression.Constant(1) });
268 | var str = expr.ToScript();
269 | Assert.AreEqual("p1[1]", str);
270 | }
271 |
272 | [TestMethod]
273 | public void TestLambda()
274 | {
275 | var p1 = Expression.Parameter(typeof(int));
276 | var func1 = Expression.Lambda(
277 | Expression.Increment(p1),
278 | p1);
279 | var expr = Expression.Block(
280 | Expression.Add(
281 | Expression.Invoke(func1, Expression.Constant(1)),
282 | Expression.Invoke(func1, Expression.Constant(1))));
283 | var str = expr.ToScript();
284 | Assert.AreEqual(@"
285 | func1(1) + func1(1);
286 |
287 | private int func1(int p1)
288 | {
289 | return p1 + 1;
290 | }".Trim()
291 | , str);
292 | }
293 |
294 | [TestMethod]
295 | public void TestLambda_Inline()
296 | {
297 | Expression, IQueryable>> fn = q => q.Where(it => it > 0);
298 | var str = fn.ToScript();
299 | Assert.AreEqual(@"
300 | public IQueryable Main(IQueryable q)
301 | {
302 | return q.Where(it => it > 0);
303 | }".Trim()
304 | , str);
305 | }
306 |
307 | [TestMethod]
308 | public void TestListInit()
309 | {
310 | Expression>> list = () => new List() { 1, 2, 3 };
311 | var str = list.ToScript();
312 | Assert.AreEqual(@"
313 | public List Main()
314 | {
315 | return new List() {1, 2, 3};
316 | }".Trim()
317 | , str);
318 | }
319 |
320 | [TestMethod]
321 | public void TestListInit_Dictionary()
322 | {
323 | Expression>> list = () => new Dictionary()
324 | {
325 | {1, 2},
326 | {3, 4}
327 | };
328 | var str = list.ToScript();
329 | Assert.AreEqual(@"
330 | public Dictionary Main()
331 | {
332 | return new Dictionary() {{1, 2}, {3, 4}};
333 | }".Trim()
334 | , str);
335 | }
336 |
337 | [TestMethod]
338 | public void TestLoop()
339 | {
340 | var @break = Expression.Label();
341 | var @continue = Expression.Label();
342 | var @return = Expression.Label();
343 | var p1 = Expression.Parameter(typeof(int));
344 | var expr = Expression.Loop(
345 | Expression.Condition(
346 | Expression.GreaterThanOrEqual(p1, Expression.Constant(1)),
347 | Expression.Condition(
348 | Expression.Equal(p1, Expression.Constant(1)),
349 | Expression.Return(@return, p1),
350 | Expression.Block(
351 | Expression.PostDecrementAssign(p1),
352 | Expression.Continue(@continue))),
353 | Expression.Break(@break)),
354 | @break,
355 | @continue);
356 |
357 | var str = expr.ToScript();
358 | Assert.AreEqual(@"
359 | while (p1 >= 1)
360 | {
361 | if (p1 == 1)
362 | {
363 | return p1;
364 | }
365 | else
366 | {
367 | p1--;
368 | continue;
369 | }
370 | }".Trim()
371 | , str);
372 | }
373 |
374 | [TestMethod]
375 | public void TestLoop2()
376 | {
377 | var label = Expression.Label();
378 | var expr = Expression.Block(
379 | Expression.Loop(Expression.Goto(label)),
380 | Expression.Label(label));
381 | var str = expr.ToScript();
382 | Assert.AreEqual(@"
383 | while (true)
384 | {
385 | goto label1;
386 | }
387 | label1:".Trim()
388 | , str);
389 | }
390 |
391 | [TestMethod]
392 | public void TestMemberAccess()
393 | {
394 | Expression> fn = () => DateTime.Now.Day;
395 | var str = fn.ToScript();
396 | Assert.AreEqual(@"
397 | public int Main()
398 | {
399 | return DateTime.Now.Day;
400 | }".Trim()
401 | , str);
402 | }
403 |
404 | private class Poco
405 | {
406 | public string Name { get; set; }
407 | public Poco Parent { get; } = new Poco();
408 | public List Children { get; } = new List();
409 | }
410 |
411 | [TestMethod]
412 | public void TestMemberInit()
413 | {
414 | Expression> fn = () => new Poco()
415 | {
416 | Name = "1",
417 | Parent = { Name = "2" },
418 | Children = { new Poco(), new Poco() }
419 | };
420 | var str = fn.ToScript();
421 | Assert.AreEqual(@"
422 | public DebugInfoInjectorTest.Poco Main()
423 | {
424 | return new DebugInfoInjectorTest.Poco()
425 | {
426 | Name = ""1"",
427 | Parent = {Name = ""2""},
428 | Children = {new DebugInfoInjectorTest.Poco(), new DebugInfoInjectorTest.Poco()}
429 | };
430 | }".Trim()
431 | , str);
432 | }
433 |
434 | [TestMethod]
435 | public void TestMethodCall_Default()
436 | {
437 | Expression, int>> fn = dict => dict[0];
438 | var str = fn.ToScript();
439 | Assert.AreEqual(@"
440 | public int Main(Dictionary dict)
441 | {
442 | return dict[0];
443 | }".Trim()
444 | , str);
445 | }
446 |
447 | [TestMethod]
448 | public void TestMethodCall()
449 | {
450 | Expression, string>> fn = list => list.ToString();
451 | var str = fn.ToScript();
452 | Assert.AreEqual(@"
453 | public string Main(List list)
454 | {
455 | return list.ToString();
456 | }".Trim()
457 | , str);
458 | }
459 |
460 | [TestMethod]
461 | public void TestMethodCall_Static()
462 | {
463 | Expression> fn = (a, b) => Math.Max(a, b);
464 | var str = fn.ToScript();
465 | Assert.AreEqual(@"
466 | public int Main(int a, int b)
467 | {
468 | return Math.Max(a, b);
469 | }".Trim()
470 | , str);
471 | }
472 |
473 | [TestMethod]
474 | public void TestNewArray()
475 | {
476 | Expression> fn = i => new[] { i };
477 | var str = fn.ToScript();
478 | Assert.AreEqual(@"
479 | public int[] Main(int i)
480 | {
481 | return new int[] {i};
482 | }".Trim()
483 | , str);
484 | }
485 |
486 | [TestMethod]
487 | public void TestNewArray_Bound()
488 | {
489 | Expression> fn = i => new int[i];
490 | var str = fn.ToScript();
491 | Assert.AreEqual(@"
492 | public int[] Main(int i)
493 | {
494 | return new int[i];
495 | }".Trim()
496 | , str);
497 | }
498 |
499 | [TestMethod]
500 | public void TestParameter_Reserved()
501 | {
502 | Expression> fn = @null => @null.Value;
503 | var str = fn.ToScript();
504 | Assert.AreEqual(@"
505 | public int Main(int? @null)
506 | {
507 | return @null.Value;
508 | }".Trim()
509 | , str);
510 | }
511 |
512 | [TestMethod]
513 | public void TestSwitch()
514 | {
515 | var p1 = Expression.Parameter(typeof(int));
516 | var expr = Expression.Switch(
517 | p1,
518 | Expression.Constant(0),
519 | Expression.SwitchCase(Expression.Constant(1), Expression.Constant(1)));
520 | var str = expr.ToScript();
521 | Assert.AreEqual(@"
522 | switch (p1)
523 | {
524 | case 1:
525 | 1;
526 | break;
527 | default:
528 | 0;
529 | break;
530 | }".Trim()
531 | , str);
532 | }
533 |
534 | [TestMethod]
535 | public void TestTryCatch()
536 | {
537 | var p1 = Expression.Parameter(typeof(double));
538 | var tryCatch = Expression.TryCatchFinally(
539 | Expression.Block(
540 | typeof(void),
541 | Expression.Assign(
542 | p1,
543 | Expression.Divide(Expression.Constant(1.0), Expression.Constant(0.0)))),
544 | Expression.Throw(Expression.New(typeof(NotSupportedException))),
545 | Expression.Catch(
546 | Expression.Parameter(typeof(DivideByZeroException)),
547 | Expression.Rethrow(),
548 | Expression.Constant(true)));
549 | var str = tryCatch.ToScript();
550 | Assert.AreEqual(@"
551 | try
552 | {
553 | p1 = 1d / 0d;
554 | }
555 | catch (DivideByZeroException p2) when (true)
556 | {
557 | throw;
558 | }
559 | finally
560 | {
561 | throw new NotSupportedException();
562 | }".Trim()
563 | , str);
564 | }
565 |
566 | [TestMethod]
567 | public void TestTryFault()
568 | {
569 | var expr = Expression.TryFault(
570 | Expression.Constant(1),
571 | Expression.Constant("blah"));
572 | var str = expr.ToScript();
573 | Assert.AreEqual(@"
574 | bool fault1 = true;
575 | try
576 | {
577 | 1;
578 | fault1 = false;
579 | }
580 | finally
581 | {
582 | if (fault1)
583 | {
584 | ""blah"";
585 | }
586 | }".Trim()
587 | , str);
588 | }
589 |
590 | [TestMethod]
591 | public void TestTypeBinary()
592 | {
593 | Expression> fn = o => o is Array;
594 | var str = fn.ToScript();
595 | Assert.AreEqual(@"
596 | public bool Main(object o)
597 | {
598 | return o is Array;
599 | }".Trim()
600 | , str);
601 | }
602 |
603 | [TestMethod]
604 | public void TestUnary_Convert()
605 | {
606 | Expression> fn = d => (int)d;
607 | var str = fn.ToScript();
608 | Assert.AreEqual(@"
609 | public int Main(double d)
610 | {
611 | return (int)d;
612 | }".Trim()
613 | , str);
614 | }
615 |
616 | [TestMethod]
617 | public void TestUnary_ArrayLength()
618 | {
619 | Expression> fn = a => a.Length;
620 | var str = fn.ToScript();
621 | Assert.AreEqual(@"
622 | public int Main(int[] a)
623 | {
624 | return a.Length;
625 | }".Trim()
626 | , str);
627 | }
628 |
629 | [TestMethod]
630 | public void TestUnary_As()
631 | {
632 | Expression> fn = expr => expr as UnaryExpression;
633 | var str = fn.ToScript();
634 | Assert.AreEqual(@"
635 | public Expression Main(Expression expr)
636 | {
637 | return expr as UnaryExpression;
638 | }".Trim()
639 | , str);
640 | }
641 |
642 | internal static int GetInternal() => 1;
643 |
644 | [TestMethod]
645 | public void TestToString()
646 | {
647 | var call = Expression.Call(
648 | typeof(DebugInfoInjectorTest).GetMethod(nameof(GetInternal),
649 | BindingFlags.Static | BindingFlags.NonPublic)
650 | );
651 | var exp = Expression.Lambda>(call);
652 | var str = exp.ToScript(new ExpressionDefinitions
653 | {
654 | IsStatic = true,
655 | MethodName = "Main",
656 | Namespace = "ExpressionDebugger.Tests",
657 | TypeName = "MockClass"
658 | });
659 | Assert.AreEqual(@"
660 | using System;
661 |
662 | namespace ExpressionDebugger.Tests
663 | {
664 | public static partial class MockClass
665 | {
666 | private static Func GetInternal1;
667 |
668 | public static int Main()
669 | {
670 | return GetInternal1.Invoke();
671 | }
672 | }
673 | }".Trim()
674 | , str);
675 | }
676 |
677 | [TestMethod]
678 | public void TestExpression()
679 | {
680 | Expression> lambda = data => new Data {Id = data.Id + "1", Records = data.Records.Select(it => it + 1)};
681 | var str = lambda.ToScript(new ExpressionDefinitions {IsExpression = true});
682 | Assert.AreEqual(@"
683 | public Expression> Main => data => new DebugInfoInjectorTest.Data()
684 | {
685 | Id = data.Id + ""1"",
686 | Records = data.Records.Select(it => it + 1)
687 | };".Trim(), str);
688 | }
689 |
690 | [TestMethod]
691 | public void TestExtensionMethod()
692 | {
693 | var p1 = Expression.Parameter(typeof(int));
694 | var p2 = Expression.Parameter(typeof(int));
695 | var lambda = Expression.Lambda>(
696 | Expression.Add(p1, p2),
697 | p1, p2);
698 | var translator = new ExpressionTranslator(new TypeDefinitions
699 | {
700 | IsStatic = true,
701 | Namespace = "ExpressionDebugger.Tests",
702 | TypeName = "MockClass"
703 | });
704 | translator.VisitLambda(lambda, ExpressionTranslator.LambdaType.ExtensionMethod, "Add");
705 | var str = translator.ToString();
706 | Assert.AreEqual(@"
707 | namespace ExpressionDebugger.Tests
708 | {
709 | public static partial class MockClass
710 | {
711 | public static int Add(this int p1, int p2)
712 | {
713 | return p1 + p2;
714 | }
715 | }
716 | }".Trim(), str);
717 | }
718 |
719 | [TestMethod]
720 | public void TestProperties()
721 | {
722 | var translator = new ExpressionTranslator(new TypeDefinitions
723 | {
724 | IsStatic = false,
725 | Namespace = "ExpressionDebugger.Tests",
726 | TypeName = "MockClass",
727 | NullableContext = 2,
728 | });
729 | translator.Properties.Add(new PropertyDefinitions
730 | {
731 | Name = "Prop1",
732 | Type = typeof(string),
733 | NullableContext = 0,
734 | });
735 | translator.Properties.Add(new PropertyDefinitions
736 | {
737 | Name = "Prop2",
738 | Type = typeof(List>),
739 | IsReadOnly = true,
740 | Nullable = new byte[] { 1, 2, 1, 2 }
741 | });
742 | translator.Properties.Add(new PropertyDefinitions
743 | {
744 | Name = "Prop3",
745 | Type = typeof(string),
746 | IsInitOnly = true,
747 | NullableContext = 0,
748 | });
749 | var str = translator.ToString();
750 | Assert.AreEqual(@"
751 | using System.Collections.Generic;
752 |
753 | namespace ExpressionDebugger.Tests
754 | {
755 | public partial class MockClass
756 | {
757 | public string Prop1 { get; set; }
758 | public List?> Prop2 { get; }
759 | public string Prop3 { get; init; }
760 |
761 | public MockClass(List?> prop2)
762 | {
763 | this.Prop2 = prop2;
764 | }
765 | }
766 | }".Trim(), str);
767 | }
768 |
769 |
770 | [TestMethod]
771 | public void TestRecordType()
772 | {
773 | var translator = new ExpressionTranslator(new TypeDefinitions
774 | {
775 | IsStatic = false,
776 | Namespace = "ExpressionDebugger.Tests",
777 | TypeName = "MockClass",
778 | IsRecordType = true,
779 | });
780 | translator.Properties.Add(new PropertyDefinitions
781 | {
782 | Name = "Prop1",
783 | Type = typeof(string)
784 | });
785 | translator.Properties.Add(new PropertyDefinitions
786 | {
787 | Name = "Prop2",
788 | Type = typeof(string),
789 | IsReadOnly = true
790 | });
791 | translator.Properties.Add(new PropertyDefinitions
792 | {
793 | Name = "Prop3",
794 | Type = typeof(string),
795 | IsInitOnly = true
796 | });
797 | var str = translator.ToString();
798 | Assert.AreEqual(@"
799 | namespace ExpressionDebugger.Tests
800 | {
801 | public partial record MockClass(string Prop2)
802 | {
803 | public string Prop1 { get; set; }
804 | public string Prop3 { get; init; }
805 | }
806 | }".Trim(), str);
807 | }
808 |
809 | public class Data
810 | {
811 | public string Id { get; set; }
812 | public IEnumerable Records { get; set; }
813 | }
814 | }
815 | }
816 |
--------------------------------------------------------------------------------
/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/ExpressionDebugger.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26228.4
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger", "ExpressionDebugger\ExpressionDebugger.csproj", "{A9B56FC4-B2BC-4A32-B1C0-B926F6259666}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger.Tests", "ExpressionDebugger.Tests\ExpressionDebugger.Tests.csproj", "{024FB14F-7B40-42E4-8B80-1D348914124C}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger.Console", "ExpressionDebugger.Console\ExpressionDebugger.Console.csproj", "{71E2EC60-6780-4AB8-9773-91B4939560FB}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionTranslator", "ExpressionTranslator\ExpressionTranslator.csproj", "{9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {024FB14F-7B40-42E4-8B80-1D348914124C}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {71E2EC60-6780-4AB8-9773-91B4939560FB}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | EndGlobal
41 |
--------------------------------------------------------------------------------
/ExpressionDebugger/ExpressionCompilationOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection;
3 |
4 | namespace ExpressionDebugger
5 | {
6 | public class ExpressionCompilationOptions
7 | {
8 | public ExpressionDefinitions? DefaultDefinitions { get; set; }
9 | public IEnumerable? References { get; set; }
10 | public bool EmitFile { get; set; }
11 | public string? RootPath { get; set; }
12 | public bool? IsRelease { get; set; }
13 | public bool ThrowOnFailedCompilation { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ExpressionDebugger/ExpressionCompiler.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.Emit;
4 | using Microsoft.CodeAnalysis.Text;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Linq.Expressions;
11 | using System.Reflection;
12 | using System.Text;
13 |
14 | namespace ExpressionDebugger
15 | {
16 | public class ExpressionCompiler
17 | {
18 | public List Translators { get; } = new List();
19 |
20 | private readonly ExpressionCompilationOptions? _options;
21 | public ExpressionCompiler(ExpressionCompilationOptions? options = null)
22 | {
23 | _options = options;
24 | }
25 |
26 | private readonly List _codes = new List();
27 | public void AddFile(string code, string filename)
28 | {
29 | var buffer = Encoding.UTF8.GetBytes(code);
30 |
31 | var path = filename;
32 | if (_options?.EmitFile == true)
33 | {
34 | var root = _options?.RootPath
35 | ?? Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "GeneratedSources");
36 | Directory.CreateDirectory(root);
37 | path = Path.Combine(root, filename);
38 | using var fs = new FileStream(path, FileMode.Create);
39 | fs.Write(buffer, 0, buffer.Length);
40 | }
41 |
42 | var sourceText = SourceText.From(buffer, buffer.Length, Encoding.UTF8, canBeEmbedded: true);
43 |
44 | var syntaxTree = CSharpSyntaxTree.ParseText(
45 | sourceText,
46 | new CSharpParseOptions(),
47 | path);
48 |
49 | var syntaxRootNode = syntaxTree.GetRoot() as CSharpSyntaxNode;
50 | var encoded = CSharpSyntaxTree.Create(syntaxRootNode, null, path, Encoding.UTF8);
51 | _codes.Add(encoded);
52 | }
53 |
54 | public void AddFile(LambdaExpression node, ExpressionDefinitions? definitions = null)
55 | {
56 | definitions ??= _options?.DefaultDefinitions ?? new ExpressionDefinitions {IsStatic = true};
57 | definitions.TypeName ??= "Program";
58 |
59 | var translator = ExpressionTranslator.Create(node, definitions);
60 | var script = translator.ToString();
61 | Translators.Add(translator);
62 |
63 | this.AddFile(script, Path.ChangeExtension(Path.GetRandomFileName(), ".cs"));
64 | }
65 |
66 | public Assembly CreateAssembly()
67 | {
68 | var references = new HashSet();
69 | references.UnionWith(from t in Translators
70 | from n in t.TypeNames
71 | select n.Key.Assembly);
72 |
73 | if (_options?.References != null)
74 | references.UnionWith(_options.References);
75 | references.Add(typeof(object).Assembly);
76 |
77 | #if NETSTANDARD2_0
78 | references.Add(Assembly.Load(new AssemblyName("netstandard")));
79 | references.Add(Assembly.Load(new AssemblyName("System.Runtime")));
80 | references.Add(Assembly.Load(new AssemblyName("System.Collections")));
81 | #endif
82 |
83 | var assemblyName = Path.GetRandomFileName();
84 | var symbolsName = Path.ChangeExtension(assemblyName, "pdb");
85 |
86 | var metadataReferences = references.Select(it => MetadataReference.CreateFromFile(it.Location));
87 | var isRelease = _options?.IsRelease ?? !Debugger.IsAttached;
88 | CSharpCompilation compilation = CSharpCompilation.Create(
89 | assemblyName,
90 | _codes,
91 | metadataReferences,
92 | new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, usings: new[] { "System" })
93 | .WithOptimizationLevel(isRelease ? OptimizationLevel.Release : OptimizationLevel.Debug)
94 | .WithPlatform(Platform.AnyCpu)
95 | );
96 |
97 | using var assemblyStream = new MemoryStream();
98 | using var symbolsStream = new MemoryStream();
99 | var emitOptions = new EmitOptions(
100 | debugInformationFormat: DebugInformationFormat.PortablePdb,
101 | pdbFilePath: symbolsName);
102 |
103 | var embeddedTexts = _codes.Select(it => EmbeddedText.FromSource(it.FilePath, it.GetText()));
104 |
105 | EmitResult result = compilation.Emit(
106 | peStream: assemblyStream,
107 | pdbStream: symbolsStream,
108 | embeddedTexts: embeddedTexts,
109 | options: emitOptions);
110 |
111 | if (!result.Success)
112 | {
113 | var errors = new List();
114 |
115 | IEnumerable failures = result.Diagnostics.Where(diagnostic =>
116 | diagnostic.IsWarningAsError ||
117 | diagnostic.Severity == DiagnosticSeverity.Error);
118 |
119 | foreach (Diagnostic diagnostic in failures)
120 | errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");
121 |
122 | throw new InvalidOperationException(string.Join("\n", errors));
123 | }
124 |
125 | assemblyStream.Seek(0, SeekOrigin.Begin);
126 | symbolsStream.Seek(0, SeekOrigin.Begin);
127 |
128 | #if NETSTANDARD2_0
129 | return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, symbolsStream);
130 | #else
131 | return Assembly.Load(assemblyStream.ToArray(), symbolsStream.ToArray());
132 | #endif
133 | }
134 |
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/ExpressionDebugger/ExpressionDebugger.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net461
5 | True
6 | Step into debugging from linq expressions
7 | True
8 | true
9 | ExpressionDebugger.snk
10 | 2.2.1
11 | enable
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ExpressionDebugger/ExpressionDebugger.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MapsterMapper/ExpressionDebugger/c4a153ea1bcfd9a02f234e2fb006423cb290c636/ExpressionDebugger/ExpressionDebugger.snk
--------------------------------------------------------------------------------
/ExpressionDebugger/ExpressionDebuggerExtensions.cs:
--------------------------------------------------------------------------------
1 | using ExpressionDebugger;
2 | using System.Diagnostics;
3 | using System.Reflection;
4 |
5 | // ReSharper disable once CheckNamespace
6 | namespace System.Linq.Expressions
7 | {
8 | public static class ExpressionDebuggerExtensions
9 | {
10 |
11 | ///
12 | /// Compile with debugging info injected
13 | ///
14 | /// Type of lambda expression
15 | /// Lambda expression
16 | /// Compilation options
17 | /// Generated method
18 | public static T CompileWithDebugInfo(this Expression node, ExpressionCompilationOptions? options = null)
19 | {
20 | return (T)(object)CompileWithDebugInfo((LambdaExpression)node, options);
21 | }
22 |
23 | public static Delegate CompileWithDebugInfo(this LambdaExpression node, ExpressionCompilationOptions? options = null)
24 | {
25 | try
26 | {
27 | var compiler = new ExpressionCompiler(options);
28 | compiler.AddFile(node);
29 | var assembly = compiler.CreateAssembly();
30 |
31 | var translator = compiler.Translators[0];
32 | return translator.CreateDelegate(assembly);
33 | }
34 | catch (Exception ex)
35 | {
36 | if (options?.ThrowOnFailedCompilation == true)
37 | throw;
38 | Debug.Print(ex.ToString());
39 | return node.Compile();
40 | }
41 | }
42 |
43 | public static Delegate CreateDelegate(this ExpressionTranslator translator, Assembly assembly)
44 | {
45 | var definitions = translator.Definitions!;
46 | var typeName = definitions.Namespace == null
47 | ? definitions.TypeName
48 | : definitions.Namespace + "." + definitions.TypeName;
49 | var type = assembly.GetType(typeName);
50 | var main = translator.Methods.First();
51 | var method = type.GetMethod(main.Key)!;
52 | var obj = definitions.IsStatic ? null : Activator.CreateInstance(type);
53 | var flag = definitions.IsStatic ? BindingFlags.Static : BindingFlags.Instance;
54 | foreach (var kvp in translator.Constants)
55 | {
56 | var field = type.GetField(kvp.Value, BindingFlags.NonPublic | flag)!;
57 | field.SetValue(obj, kvp.Key);
58 | }
59 | return method.CreateDelegate(main.Value, obj);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/ExpressionTranslator/ExpressionDefinitions.cs:
--------------------------------------------------------------------------------
1 | namespace ExpressionDebugger
2 | {
3 | public class ExpressionDefinitions : TypeDefinitions
4 | {
5 | public string? MethodName { get; set; }
6 | public bool IsExpression { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/ExpressionTranslator/ExpressionTranslator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Globalization;
6 | #if !NETSTANDARD1_3
7 | using System.Dynamic;
8 | #endif
9 | using System.IO;
10 | using System.Linq;
11 | using System.Linq.Expressions;
12 | using System.Reflection;
13 | using System.Runtime.CompilerServices;
14 |
15 | namespace ExpressionDebugger
16 | {
17 | public class ExpressionTranslator : ExpressionVisitor
18 | {
19 | private const int Tabsize = 4;
20 | private StringWriter _writer;
21 | private int _indentLevel;
22 | private List? _appendWriters;
23 |
24 | private HashSet? _usings;
25 | private Dictionary? _defaults;
26 |
27 | private Dictionary