我正在深入研究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
答案 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 ):前两个是本地人您已声明的implicitConversion
和test
,第三个是编译器生成的临时名为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
指令可用于创建值类型的新实例 堆栈,然后可以作为参数传递,存储在本地, 等