将C#表达式树保存到文件中

6

我想调试一个表达式并将表达式树保存到文件中:

var generator = DebugInfoGenerator.CreatePdbGenerator();
var document = Expression.SymbolDocument(fileName: "MyDebug.txt");
var debugInfo = Expression.DebugInfo(document, 6, 9, 6, 22);
var expressionBlock = Expression.Block(debugInfo, fooExpression);
var lambda = Expression.Lambda(expressionBlock, parameters);
lambda.CompileToMethod(method, generator);
var bakedType = type.CreateType();
return (type)bakedType.GetMethod(method.Name).Invoke(null, parameters);

如何查找或保存"MyDebug.txt"文件?

1个回答

7

您可能没有理解SymbolDocument()是什么意思...

var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("foo"), System.Reflection.Emit.AssemblyBuilderAccess.RunAndSave);
var mod = asm.DefineDynamicModule("mymod", true);
var type = mod.DefineType("baz", TypeAttributes.Public);
var method = type.DefineMethod("go", MethodAttributes.Public | MethodAttributes.Static);

Expression fooExpression = Expression.Divide(Expression.Constant(0), Expression.Constant(0));
var parameters = new ParameterExpression[0];
var generator = DebugInfoGenerator.CreatePdbGenerator();
var document = Expression.SymbolDocument(fileName: "MyDebug.txt");
var debugInfo = Expression.DebugInfo(document, 6, 9, 6, 22);
var expressionBlock = Expression.Block(debugInfo, fooExpression);
var lambda = Expression.Lambda(expressionBlock, parameters);

lambda.CompileToMethod(method, generator);
var bakedType = type.CreateType();
Func<int> method2 = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), bakedType.GetMethod(method.Name));

try
{
    int res = method2();
}
catch (Exception e)
{
    Console.WriteLine(e.StackTrace);
}

我生成的Expression类似于return 0/0;。我执行它并捕获了异常(注意,我没有使用Invoke方法,因为Invoke方法会将DivisionByZeroException作为InnerException)。
我输出的堆栈跟踪如下:
在 在ConsoleApplication85.Program.Test()
你看到了MyDebug.txt:row6?这些是你的SymbolDocumentDebugInfo
以下是来自调试动态生成代码(Reflection.Emit)的更完整示例。
static void Test()
{
    // create a dynamic assembly and module 
    AssemblyName assemblyName = new AssemblyName("HelloWorld");
    AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

    // Mark generated code as debuggable. 
    // See https://learn.microsoft.com/en-us/archive/blogs/rmbyers/debuggableattribute-and-dynamic-assemblies for explanation.        
    Type daType = typeof(DebuggableAttribute);
    ConstructorInfo daCtor = daType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) });
    CustomAttributeBuilder daBuilder = new CustomAttributeBuilder(daCtor, new object[] { 
    DebuggableAttribute.DebuggingModes.DisableOptimizations | 
    DebuggableAttribute.DebuggingModes.Default });
    assemblyBuilder.SetCustomAttribute(daBuilder);

    ModuleBuilder module = assemblyBuilder.DefineDynamicModule("HelloWorld.exe", true); // <-- pass 'true' to track debug info.

    var doc = Expression.SymbolDocument(@"Source.txt");

    // create a new type to hold our Main method 
    TypeBuilder typeBuilder = module.DefineType("HelloWorldType", TypeAttributes.Public | TypeAttributes.Class);

    // create the Main(string[] args) method 
    MethodBuilder methodbuilder = typeBuilder.DefineMethod("MyMethod", MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(string[]) });

    // Create a local variable of type 'string', and call it 'xyz'
    var localXYZ = Expression.Variable(typeof(string), "xyz"); // Provide name for the debugger. 

    var generator = DebugInfoGenerator.CreatePdbGenerator();
    var debugInfo1 = Expression.DebugInfo(doc, 2, 1, 2, 100);

    // Emit sequence point before the IL instructions. This is start line, start col, end line, end column, 

    // Line 2: xyz = "hello"; 
    var assign = Expression.Assign(localXYZ, Expression.Constant("Hello world!"));

    // Line 3: Write(xyz); 
    MethodInfo infoWriteLine = typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string) });
    var debugInfo2 = Expression.DebugInfo(doc, 3, 1, 3, 100);

    var write = Expression.Call(infoWriteLine, localXYZ);

    // Line 4: return; 
    var debugInfo3 = Expression.DebugInfo(doc, 4, 1, 4, 100);

    var block = Expression.Block(new ParameterExpression[] { localXYZ }, new Expression[] { debugInfo1, assign, debugInfo2, write, debugInfo3 });
    var lambda = Expression.Lambda<Action<string[]>>(block, Expression.Parameter(typeof(string[])));

    // bake it 
    lambda.CompileToMethod(methodbuilder, generator);
    Type helloWorldType = typeBuilder.CreateType();

    // This now calls the newly generated method. We can step into this and debug our emitted code!! 
    var dm = (Action<string[]>)Delegate.CreateDelegate(typeof(Action<string[]>), helloWorldType.GetMethod("MyMethod"));
    dm(new string[] { null }); // <-- step into this call 
}

请将此内容保存为 Source.txt

1|  // Test
2|  xyz = "hello"; 
3|  Write(xyz); 
4|  return;

然后在dm(new string[] { null })上设置断点,在bp处按F11。它会要求您选择Source.txt。请注意,如果您不选择它,您将不得不删除.suo文件才能再次获取窗口。

请注意使用DebuggableAttribute。看来这是将动态程序集标记为可调试的技巧。


然而,当我想进行逐步调试时,VS会要求我提供源文件。在这种情况下,逐步调试是否可行? - user2341923
实际上,如果我完全从你的代码中删除SymbolDocument和DebugInfo,它仍然会显示堆栈跟踪。 - user2341923
@XyiTebe 但它不会显示“MyDebug.txt”和“第6行”。这些方法用于在表达式中放置注释。我认为您无法使用Visual Studio“调试”表达式树。 - xanatos
使用Reflection.Emit,创建.il文件并使用该文件进行代码调试是成功的;但使用表达式树还未成功。参考资料:Hazzard&Bock的《.Net元编程》,第160页和184页。 - user2341923
@XyiTebe 你是如何创建 il 文件的?是手动创建的还是其他方式? - xanatos
显示剩余4条评论

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