9

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

    private static void HowNullableWorks()
    {
        int test = 3;
        int? implicitConversion = test;
        Nullable<int> test2 = new Nullable<int>(3);

        MethodThatTakesNullableInt(null);
        MethodThatTakesNullableInt(39);
    }

我很惊讶地看到implicitConversion / test2变量被初始化为:

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

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

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

IL_0026:  newobj     instance void valuetype [mscorlib]System.Nullable`1<int32>::.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<int32> implicitConversion,
           [2] valuetype [mscorlib]System.Nullable`1<int32> test2,
           [3] valuetype [mscorlib]System.Nullable`1<int32> 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<int32>::.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<int32>::.ctor(!0)
  IL_0014:  nop
  IL_0015:  ldloca.s   CS$0$0000
  IL_0017:  initobj    valuetype [mscorlib]System.Nullable`1<int32>
  IL_001d:  ldloc.3
  IL_001e:  call       void csharp.in.depth._2nd.Program::MethodThatTakesNullableInt(valuetype [mscorlib]System.Nullable`1<int32>)
  IL_0023:  nop
  IL_0024:  ldc.i4.s   39
  IL_0026:  newobj     instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
  IL_002b:  call       void csharp.in.depth._2nd.Program::MethodThatTakesNullableInt(valuetype [mscorlib]System.Nullable`1<int32>)
  IL_0030:  nop
  IL_0031:  ret
} // end of method Program::HowNullableWorks
4

1 回答 1

3

首先,看起来您已经在 Debug 模式下编译(基于nops) - 如果您在 Release 模式下编译,您可能会看到发出不同的代码。

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

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

代码中可空类型的前三种用法导致 null 值存储在局部变量中,因此此注释是相关的(局部变量是值的一种类型:前两个是局部变量implicitConversion并且test您已声明,第三个是编译器生成的临时文件,名为CS$0$0000. 正如 ECMA 规范所指出的,这些局部变量可以通过使用initobj(相当于结构的默认无参数构造函数,CS$0$0000在这种情况下用于)或通过加载本地地址并调用构造函数(用于其他两个当地人)。

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

您可能想知道为什么编译器会为调用生成本地MethodThatTakesNullableInt(null)而不是为MethodThatTakesNullableInt(39). 我怀疑答案是编译器总是使用initobj调用默认构造函数(然后需要一个本地或其他 home 作为值),但是newobj当还没有合适的 home 时用于调用其他构造函数并将结果存储在堆栈中为价值。

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

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

于 2012-08-15T16:22:09.127 回答