我正在深入研究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)
首先,看起来您是在调试模式下编译的(基于 s nop) - 如果您在发布模式下编译,您可能会看到发出不同的代码。
ECMA CLR 规范的 I.12.1.6.2.1 节(初始化值类型的实例)说:
\n\n\n\n\n可以使用三个选项来初始化值类型实例的 home。您可以通过加载家庭地址(参见表 I.8:家庭位置的地址和类型)并使用
\ninitobj\n 指令(对于局部变量,这也可以通过设置localsinit方法中的位来完成)将其归零\xe2\x80\x99s 标头)。您可以通过加载家庭地址(参见表 I.8:家庭位置的地址和类型)然后直接调用构造函数来调用用户定义的构造函数。或者,您可以将现有实例复制到\n 主目录,如 \xc2\xa7I.12.1.6.2.2 中所述。
代码中前三次使用可空类型会导致存储在局部变量中的空值,因此此注释是相关的(局部变量是值的一种类型):前两个是局部变量implicitConversion并且test是您已声明的,第三个是局部变量是编译器生成的临时文件,称为CS$0$0000. 正如 ECMA 规范所示,这些局部变量可以通过使用initobj(相当于结构体的默认无参数构造函数,CS$0$0000在本例中使用)或通过加载局部变量的地址并调用构造函数(用于另外两名当地人)。
但是,对于最终可为 null 的实例(通过从 隐式转换创建39),结果不会存储在本地 - 它是在堆栈上生成的,因此初始化 home 的规则不适用于此处。相反,编译器仅用于newobj在堆栈上创建值(就像任何值或引用类型一样)。
您可能想知道为什么编译器为调用生成本地变量MethodThatTakesNullableInt(null)而不是为MethodThatTakesNullableInt(39). 我怀疑答案是编译器总是使用initobj调用默认构造函数(然后需要本地或其他 home 来存储该值),但newobj在还没有合适的 home 时使用调用其他构造函数并将结果存储在堆栈上为了价值。
有关更多信息,另请参阅规范第 III.4.21 节 (newobj) 中的注释:
\n\n\n\n值类型通常不使用创建
\nnewobj。它们通常被分配为参数或局部变量newarr(对于从零开始的一维数组),或者作为对象的字段。一旦分配,它们就会使用 进行初始化initobj。但是,newobj\n 指令可用于在堆栈上创建\n 值类型的新实例,然后可以将其作为参数传递、存储在本地等中。
| 归档时间: |
|
| 查看次数: |
1610 次 |
| 最近记录: |