Search code examples
c#.netreflectioncilreflection.emit

Different IL codes for same method body


Let's suppose I have the following class:

public class SomeClass
{
    public int GetValue()
    {
        return 1;
    }
}

Inspecting the generated IL code for this method:

byte[] methodBody = typeof(SomeClass).GetMethod("GetValue").GetMethodBody().GetILAsByteArray();

We get that methodBody is:

[0, 23, 10, 43, 0, 6, 42] -- 7 bytes

Creating my own method using Reflection.Emit:

MethodBuilder methodBuilder = typeBuilder.DefineMethod("GetValue", MethodAttributes.Public, typeof(int), Type.EmptyTypes);
ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldc_I4, 1);
il.Emit(OpCodes.Ret);

//....

byte[] dynamicMethodBody = dynamicType.GetMethod("GetValue").GetMethodBody().GetILAsByteArray();

We get that dynamicMethodBody is:

[32, 1, 0, 0, 0, 42] -- 6 bytes

Why are the two method bodies different? Aren't they exactly the same?

Furthermore, I'm guessing the first two bytes 32 and 1 in my dynamicMethodBody have something to do with loading the constant 1 to the evaluation stack, but why aren't these two bytes present in methodBody?


Solution

  • If you compile SomeClass in debug mode, the compiler will insert a lot of extra stuff just to make the debugging experience better. Turns out optimized IL can be a lot easier to read in simple cases.

    For the compiler generated body, I believe it is generating a noop (0), Ldc_I4_1 (23), then some storage in a local and a branch instruction (which I'm not sure I follow), followed by a Ret (42). This is based on the decompiled code in debug:

    IL_0000: nop
    IL_0001: ldc.i4.1
    IL_0002: stloc.0
    IL_0003: br.s IL_0005
    IL_0005: ldloc.0
    IL_0006: ret
    

    You can see in decompiled release code that the instructions are much simpler:

    IL_0000: ldc.i4.1
    IL_0001: ret
    

    I believe the dynamicMethodBody is 1 byte for Ldc_I4 (32), 4 bytes for the integer 1, (1 0 0 0), and then Ret (42).

    So your code is a bit more verbose than the compiler would generate in release mode, since there is an opcode for the constant 32-bit integer 1 built-in.