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
?
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.