Saving C# Expression Tree to a File
Asked Answered
C

1

6

I want to debug an expression and save the expression tree to a file:

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

How can I find or save "MyDebug.txt"?

Cannoneer answered 15/9, 2013 at 14:5 Comment(0)
P
7

You didn't comprehend what SymbolDocument() is...

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

The Expression I generated is something like return 0/0;. I execute it and trap the exception (note that I don't use the Invoke method, because the Invoke method would put the DivisionByZeroException as the InnerException).

The stack trace I output is something like:

in baz.go() in MyDebug.txt:row 6 in ConsoleApplication85.Program.Test()

You see the MyDebug.txt:row 6? They are your SymbolDocument and your DebugInfo.

A more complete example, taken from Debugging Dynamically Generated Code (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 
}

Save this somewhere as Source.txt

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

then put a breakpoint on the dm(new string[] { null }), and press F11 when on the bp. It will ask you for the Source.txt. Note that if you don't select it, you'll have to delete the .suo file to get the window a second time.

Note the use of the DebuggableAttribute. It seems it's the trick to mark the dynamic assembly as debuggable.

Pestiferous answered 16/9, 2013 at 10:3 Comment(9)
Still, when I want to make step debugging, VS asks me for the source file. Is the step debugging possible in this case?Cannoneer
Actually, if I remove SymbolDocument and DebugInfo from your code completely, it will still show the stack trace.Cannoneer
@XyiTebe But it won't show "MyDebug.txt" and "row 6". Those methods are used to put annotations in the Expression. I don't think you can "debug" Expression trees with Visual StudioPestiferous
With Reflection.Emit, creating .il file and step debugging code with the created file was successful; with expression trees not yet. For reference: Hazzard & Bock "Metaprogramming in .Net", pp. 160 and 184 resp.Cannoneer
@XyiTebe How did you create the il file? Manually or how?Pestiferous
@XyiTebe Added something to the response.Pestiferous
To compile .il from Reflection.Emit see: manning.com/hazzard , in the source code for this book, Chapter 5, ReflectionEmitWithDebuggingMethodGenerator.cs and related files. There you can also find sample Chapter 6 - about Expressions. My question relates to pp.185-186Cannoneer
@XyiTebe The author "cheats" and doesn't tell you that there is no auto Expression generator and that you have to do everything by hand :-) You'll need to create an expression visitor to add the Expression.DebugInfo plus something to generate the .exp file. Quite complex (probably 1 week work to do it correctly). There is a forum about the book manning-sandbox.com/forum.jspa?forumID=732&start=0, you could ask the author there.Pestiferous
Seems that will be the answer - and a fun task to do on the free time. Thank you!Cannoneer

© 2022 - 2024 — McMap. All rights reserved.