ToString on Expression Trees produces badly formatted output

匿名 (未验证) 提交于 2019-12-03 01:22:02

问题:

When I use Expression.ToString() to convert an Expression Tree into human readable form, the result is something like this:

x => ((x.ID > 2) OrElse (x.ID != 6)) x => ((x.ID > 2) AndAlso (x.ID != 6))

Ideally, I would want the output to show the operators instead of "OrElse" and "AndAlso":

x => ((x.ID > 2) || (x.ID != 6)) x => ((x.ID > 2) && (x.ID != 6))

As a workaround, I could use the string.Replace() method..

.Replace("AndAlso", "&&") .Replace("OrElse", "||")

but that has obvious weaknesses and seems awkward. Also I do not want to create a large 'Replace'-section or huge regex-tree simply to get the formatting right.

Is there a simple way to get a code-like human-readable form of expression trees?

回答1:

Unfortunately the simplest way to do this correctly would be to write your own ExpressionVisitor class which produces the C#-formatted output code.

The simplest way to do this is to use the ExpressionStringBuilder from the reference sources as a starting point and to tweak it until you are happy with the output result.



回答2:

When I'm interested in the semantics of the code represented by the expression, rather than the exact syntax tree, I've found it very useful to compile it to an Assembly and view that in ILSpy. Convenience method:

// Code is probably adapted from some other answer, don't remember public static void CompileToAssemblyFile(   this LambdaExpression expression,   string outputFilePath = null,   string assemblyAndModuleName = null,   string typeName = "TheType",   string methodName = "TheMethod",   // Adjust this   string ilSpyPath = @"C:\path\to\ILSpy.exe") {   assemblyAndModuleName = assemblyAndModuleName ?? nameof(CompileToAssemblyFile);    outputFilePath = outputFilePath ??                    Path.Combine(                      Path.GetTempPath(),                      $"{assemblyAndModuleName}_{DateTime.Now:yyyy-MM-dd_HH_mm_ss}_{Guid.NewGuid()}.dll");    var domain = AppDomain.CurrentDomain;   var asmName = new AssemblyName {Name = assemblyAndModuleName};    var asmBuilder = domain.DefineDynamicAssembly(     asmName,     AssemblyBuilderAccess.RunAndSave,     Path.GetDirectoryName(outputFilePath));    string outputFileName = Path.GetFileName(outputFilePath);    var module = asmBuilder.DefineDynamicModule(     assemblyAndModuleName,     outputFileName,     true);    var typeBuilder = module.DefineType(typeName, TypeAttributes.Public);    var methodBuilder = typeBuilder.DefineMethod(     methodName,     MethodAttributes.Public | MethodAttributes.Static,     expression.ReturnType,     expression.Parameters.Select(p => p.Type).ToArray());    var pdbGenerator = DebugInfoGenerator.CreatePdbGenerator();    expression.CompileToMethod(methodBuilder, pdbGenerator);    typeBuilder.CreateType();    asmBuilder.Save(outputFileName);    Process.Start(ilSpyPath, outputFilePath); }

(This is not very faithful to the syntax tree because it goes through both the Expression -> IL translation done by the LambdaCompiler, and the IL -> C# decompilation by ILSpy. OTOH, it can improve readability by converting some gotos into loops, and by producing actual C#.)

This will fail if the Expression contains "non-trivial constants" (live objects); but for that one could write a visitor that replaces constants by new variables and then lambda-abstracts these variables at the toplevel.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!