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

时间:2012-08-15 09:28:57

标签: c# il

我正在深入研究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

1 个答案:

答案 0 :(得分:2)

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

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

  

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

中所述

代码中可空类型的前三个用法导致存储在本地的空值,因此这个注释是相关的(本地人是值的一种 home ):前两个是本地人您已声明的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(for。)作为参数或局部变量分配   基于零的一维数组),或作为对象的字段。一旦   已分配,使用initobj初始化它们。但是,newobj   指令可用于创建值类型的新实例   堆栈,然后可以作为参数传递,存储在本地,   等