相同方法体的不同 IL 代码

Different IL codes for same method body

假设我有以下 class:

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

检查为此方法生成的 IL 代码:

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

我们得到 methodBody 是:

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

使用 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();

我们得到 dynamicMethodBody 是:

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

为什么两个方法体不同?他们不完全一样吗?

此外,我猜测 dynamicMethodBody 中的前两个字节 321 与将常量 1 加载到计算堆栈有关,但为什么 methodBody 中不存在这两个字节?

如果你在调试模式下编译SomeClass,编译器会插入很多额外的东西,只是为了让调试体验更好。事实证明,优化的 IL 在简单情况下更容易阅读。

对于编译器生成的主体,我相信它会生成一个 noop (0),Ldc_I4_1 (23),然后是本地的一些存储和一个分支指令(我不确定我是否遵循),然后是 Ret (42)。这是基于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

在反编译的release代码中可以看到指令简单多了:

IL_0000: ldc.i4.1
IL_0001: ret

我相信 dynamicMethodBody 对于 Ldc_I4 (32) 是 1 个字节,对于整数 1 (1 0 0 0) 是 4 个字节,然后是 Ret (42)。

所以你的代码比编译器在发布模式下生成的代码要冗长一点,因为有一个内置常量 32 位整数 1 的操作码。