IL中调用实例与newobj实例的区别

我正在深入研究C#,并使用可空的值类型。 出于实验目的,我写了一段代码:

private static void HowNullableWorks() { int test = 3; int? implicitConversion = test; Nullable test2 = new Nullable(3); MethodThatTakesNullableInt(null); MethodThatTakesNullableInt(39); } 

而且我被要求看到implicitConversion / test2变量初始化为:

 call instance void valuetype [mscorlib]System.Nullable`1::.ctor(!0) 

指令,而当调用MethodThatTakesNullableInt时,我可以看到:

 IL_0017: initobj valuetype [mscorlib]System.Nullable`1 

 IL_0026: newobj instance void valuetype [mscorlib]System.Nullable`1::.ctor(!0) 

我明白了。 我以为我也会看到用于implicitConversion / test2的 newobj指令。

这是完整的IL代码:

 .method private hidebysig static void HowNullableWorks() cil managed { // Code size 50 (0x32) .maxstack 2 .locals init ([0] int32 test, [1] valuetype [mscorlib]System.Nullable`1 implicitConversion, [2] valuetype [mscorlib]System.Nullable`1 test2, [3] valuetype [mscorlib]System.Nullable`1 CS$0$0000) IL_0000: nop IL_0001: ldc.i4.3 IL_0002: stloc.0 IL_0003: ldloca.s implicitConversion IL_0005: ldloc.0 IL_0006: call instance void valuetype [mscorlib]System.Nullable`1::.ctor(!0) IL_000b: nop IL_000c: ldloca.s test2 IL_000e: ldc.i4.3 IL_000f: call instance void valuetype [mscorlib]System.Nullable`1::.ctor(!0) IL_0014: nop IL_0015: ldloca.s CS$0$0000 IL_0017: initobj valuetype [mscorlib]System.Nullable`1 IL_001d: ldloc.3 IL_001e: call void csharp.in.depth._2nd.Program::MethodThatTakesNullableInt(valuetype [mscorlib]System.Nullable`1) IL_0023: nop IL_0024: ldc.i4.s 39 IL_0026: newobj instance void valuetype [mscorlib]System.Nullable`1::.ctor(!0) IL_002b: call void csharp.in.depth._2nd.Program::MethodThatTakesNullableInt(valuetype [mscorlib]System.Nullable`1) IL_0030: nop IL_0031: ret } // end of method Program::HowNullableWorks 

首先,看起来你已经在Debug模式下编译了(基于nop ) – 如果你在Release模式下编译,你可能会看到不同的代码。

ECMA CLR规范的第I.12.1.6.2.1节(初始化值类型的实例)说:

初始化值类型实例的主页有三个选项。 您可以通过加载home的地址(参见表I.8:Home Locations的地址和类型)并使用initobj指令将其initobj (对于局部变量,这也可以通过在方法的头中设置localsinit位来实现)。 您可以通过加载home的地址来调用用户定义的构造函数(请参阅表I.8:Home Locations的地址和类型),然后直接调用构造函数。 或者您可以将现有实例复制到主目录中,如§I.12.1.6.2.2中所述。

代码中可空类型的前三个用法导致存储在locals中的空值,因此这个注释是相关的(locals是值的一种类型的home ):前两个是你声明的locals implicitConversiontest ,以及第三个是编译器生成的临时名为CS$0$0000 。 正如ECMA规范所指出的那样,可以使用initobj (相当于结构的默认no-args构造函数,在这种情况下用于CS$0$0000 )或通过加载本地地址并调用构造函数来初始化这些本地。 (用于其他两个当地人)。

但是,对于最终的可空实例(由39的隐式转换创建),结果不会存储在本地 – 它是在堆栈上生成的,因此初始化主页的规则不适用于此处。 相反,编译器只使用newobj在堆栈上创建值(就像任何值或引用类型一样)。

您可能想知道为什么编译器为MethodThatTakesNullableInt(null)调用生成了本地,但没有为MethodThatTakesNullableInt(39)生成本地。 我怀疑答案是编译器总是使用initobj来调用默认构造函数(然后需要一个本地或其他主页来获取值),但是使用newobj来调用其他构造函数并将结果存储在堆栈上时还没有适合家居的价值。

有关更多信息,请参阅规范中的第III.4.21节(newobj)中的此注释:

通常不使用newobj创建值类型。 它们通常使用newarr (对于从零开始的一维数组)或作为对象的字段分配为参数或局部变量。 分配后,使用initobj初始化initobj 。 但是, newobj指令可用于在堆栈上创建值类型的新实例,然后可以作为参数传递,存储在本地等等。