Search code examples
c#lambdaexpressionexpression-trees

Do Extra BlockExpressions inside other BlockExpressions impact code generation?


In C# Expression Trees,

.Block(
    ConsoleApplication2.A $var1,
    ConsoleApplication2.B $var2) {
     .Block() {
        .Block() {
           $var1 = .New ConsoleApplication2.A();
           $var1
        };
        .Block() {
           $var2 = .New ConsoleApplication2.B($var1);
           $var2
        }
   }
}

Does the above expression generate poorer quality of code vs.

.Block(
    ConsoleApplication2.A $var1,
    ConsoleApplication2.B $var2) {
     $var1 = .New ConsoleApplication2.A();
     $var2 = .New ConsoleApplication2.B($var1);
}

They are functionally equivalent, but the first one is what I often end up getting by doing visitor pattern based modifications.

Is there a way to "Reduce" or "Collapse" all Blocks that are placeholders (i.e. no variables??) into the parent blocks? Maybe using yet another visitor? I couldn't figure it out.


Solution

  • At the IL level, there is no concept of blocks, so I would expect both expressions to results in the same (or very similar) IL code.

    To verify this, you can use Reflection.Emit and CompileToMethod() to create an assembly that has both options in separate methods. You can then look at the IL of both methods using ildasm to verify that the code is indeed the same:

    .locals init (
        [0] class [ConsoleApplication1]ConsoleApplication1.A a,
        [1] class [ConsoleApplication1]ConsoleApplication1.B b)
    L_0000: newobj instance void [ConsoleApplication1]ConsoleApplication1.A::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: newobj instance void [ConsoleApplication1]ConsoleApplication1.B::.ctor(class [ConsoleApplication1]ConsoleApplication1.A)
    L_000c: stloc.1 
    L_000d: ret 
    

    The code to create the assembly could look like this:

    var var1 = Expression.Variable(typeof(A), "var1");
    var var2 = Expression.Variable(typeof(B), "var2");
    
    Expression<Action> withBlocks =
        Expression.Lambda<Action>(
            Expression.Block(
                new[] { var1, var2 },
                Expression.Block(
                    Expression.Block(
                        Expression.Assign(
                            var1,
                            Expression.New(typeof(A).GetConstructors().Single())),
                        var1),
                    Expression.Block(
                        Expression.Assign(
                            var2,
                            Expression.New(
                                typeof(B).GetConstructors().Single(), var1)),
                        var2))));
    
    Expression<Action> withoutBlocks =
        Expression.Lambda<Action>(
            Expression.Block(
                new[] { var1, var2 },
                Expression.Assign(
                    var1, Expression.New(typeof(A).GetConstructors().Single())),
                Expression.Assign(
                    var2,
                    Expression.New(typeof(B).GetConstructors().Single(), var1))));
    
    var assembly = AssemblyBuilder.DefineDynamicAssembly(
        new AssemblyName("test"), AssemblyBuilderAccess.Save);
    var module = assembly.DefineDynamicModule("test.dll");
    var type = module.DefineType("Type");
    
    var withBlocksMethod = type.DefineMethod(
        "WithBlocks", MethodAttributes.Public | MethodAttributes.Static,
        typeof(void), Type.EmptyTypes);
    withBlocks.CompileToMethod(withBlocksMethod);
    
    var withoutBlocksMethod = type.DefineMethod(
        "WithoutBlocks", MethodAttributes.Public | MethodAttributes.Static,
        typeof(void), Type.EmptyTypes);
    withoutBlocks.CompileToMethod(withoutBlocksMethod);
    
    type.CreateType();
    
    assembly.Save("test.dll");