Expression Trees中的ToString方法输出格式不佳

4
当我使用Expression.ToString()将表达式树转换为人类可读形式时,结果类似于这样:
x => ((x.ID > 2) OrElse (x.ID != 6))
x => ((x.ID > 2) AndAlso (x.ID != 6))

理想情况下,我希望输出显示操作符而不是 "OrElse" 和 "AndAlso":
x => ((x.ID > 2) || (x.ID != 6))
x => ((x.ID > 2) && (x.ID != 6))

作为一种解决方法,我可以使用 string.Replace() 方法。

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

但这种方法有明显的弱点,而且似乎有些笨重。另外,我不想创建一个大的“替换”部分或庞大的正则表达式树来正确地获取格式。

有没有一种简单的方式来获得一种类似代码且易于阅读的表达树形式?


1
由于表达式不是特定于C#的,我不知道有没有其他方法可以输出C#精确语法。我认为你最好还是使用String.Replace方法。如果你担心可能会出现类似的变量名误报,可以尝试匹配周围的空格.Replace(" AndAlso ", " && ")。编辑:关于你为什么要寻找替代方案,只需将此转换行为放入共享实用程序方法中即可。这样,如果您需要添加新的“Replace”,可以在一个位置完成。 - Chris Sinclair
2个回答

0

当我对表达式所代表的代码的语义感兴趣时,而不是精确的语法树时,我发现将其编译为汇编并在ILSpy中查看非常有用。方便方法:

// 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);
}

这并不是非常忠实于语法树,因为它经过了LambdaCompiler完成的Expression -> IL翻译和IL -> C#反编译的两个步骤。然而,它可以通过将一些goto转换为循环以及生成实际的C#来提高可读性。

如果Expression包含“非平凡常量”(活动对象),则此方法会失败;但是,可以编写一个访问者,将常量替换为新变量,然后在顶层对这些变量进行lambda抽象化。


0

不幸的是,正确完成此操作的最简单方法是编写自己的ExpressionVisitor类,该类生成C#格式的输出代码。

完成此操作的最简单方法是使用ExpressionStringBuilder作为起点,并进行微调,直到满意为止。


1
这里不需要使用ILSpy。http://referencesource.microsoft.com/#System.Core/Microsoft/Scripting/Ast/ExpressionStringBuilder.cs#240c0ae863272266 - MarcinJuraszek
谢谢。我没注意检查这些资源是否已发布 :) - M.Stramm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接