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

dra*_*fly 8 c# il

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

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

        MethodThatTakesNullableInt(null);
        MethodThatTakesNullableInt(39);
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

IL_0017:  initobj    valuetype [mscorlib]System.Nullable`1<int32>
Run Code Online (Sandbox Code Playgroud)

IL_0026:  newobj     instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
Run Code Online (Sandbox Code Playgroud)

我明白了 我以为我也会看到用于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
Run Code Online (Sandbox Code Playgroud)

kvb*_*kvb 4

首先,看起来您是在调试模式下编译的(基于 s nop) - 如果您在发布模式下编译,您可能会看到发出不同的代码。

\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n
\n

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

\n
\n