如何翻译;默认值(SomeType)";从C#到CIL?
我目前正在处理一个涉及如何翻译;默认值(SomeType)";从C#到CIL?,c#,default,cil,C#,Default,Cil,我目前正在处理一个涉及System.Reflection.Emit代码生成的问题。我正试图找出在C#中使用default(SomeType)的地方应该发出什么样的CIL 我已经在VisualStudio11Beta中运行了一些基本实验。显示了default(bool)、default(string)和default(int?)的以下CIL输出: .locals init ( [0] bool V_0,
System.Reflection.Emit
代码生成的问题。我正试图找出在C#中使用default(SomeType)
的地方应该发出什么样的CIL
我已经在VisualStudio11Beta中运行了一些基本实验。显示了default(bool)
、default(string)
和default(int?
)的以下CIL输出:
.locals init (
[0] bool V_0,
[1] string V_1,
[2] valuetype [mscorlib]System.Nullable`1<int32> V_2
)
// bool b = default(bool);
ldc.i4.0
stloc.0
// string s = default(string);
ldnull
stloc.1
// int? ni = default(int?);
ldloca.s V_2
initobj valuetype [mscorlib]System.Nullable`1<int32>
所有三种方法产生相同的CIL方法主体:
.locals init (
[0] !!T V_0,
[1] !!T V_1
)
IL_0000: nop
IL_0001: ldloca.s V_1
IL_0003: initobj !!T
IL_0009: ldloc.1
IL_000a: stloc.0
IL_000b: br.s IL_000d
IL_000d: ldloc.0
IL_000e: ret
问题: 我能否从所有这些中得出结论,C#的
default(SomeType)
与CIL的最接近
用于非基元类型(字符串除外)?)initobj
/ldc.iX.0
/等。对于基本类型(加上ldnull
)string
为什么
CreateClassNull
不仅转换为ldnull
,而是转换为initobj
?毕竟,ldnull
是为string
发出的(这也是一种引用类型)是的,default
就是这么做的。你正确地推断它只是基本上0
(或等价物)的语法糖
我能否从所有这些中得出结论,C#的default(SomeType)
与CIL的initobj
对于非基本类型和ldc.i4.0
,ldnull
等对于基本类型最为接近
这是一个合理的总结,但更好的思考方式是:如果C#编译器将default(T)
分类为编译时常量,则会发出该常量的值。对于数值类型,该值为零;对于bool,该值为false;对于任何引用类型,该值为null。如果该值不会分类为常量,则必须(1)发出一个临时变量,(2)获取临时变量的地址,(3)通过其地址初始化该临时变量,(4)确保临时变量的值在需要时位于堆栈上
为什么CreateClassNull
不仅转换为ldnull,而是转换为initobj
好吧,让我们按照你的方式来做,看看会发生什么:
... etc
.class private auto ansi beforefieldinit P
extends [mscorlib]System.Object
{
.method private hidebysig static !!T M<class T>() cil managed
{
.maxstack 1
ldnull
ret
}
... etc
这可能就是我们不这样做的原因。您对“基元”类型是什么意思?有值类型和引用类型。我希望ldnull与引用类型(包括字符串)一起使用。\n对于“基元类型”,我指的是CTS(?)本机支持的类型--
bool
,byte
,char
,int
,float
,double
,等等。与用户定义的值或引用类型不同,这些类型不是复合类型,不能分解为更基本的类型。我注意到,您必须查看调试的、未优化的codegen,因为编译器没有重新编译将一个分支移到下一条指令。@Eric,我想是的。上面的CIL来自使用“AnyCPU”的“调试”配置构建的程序集。@Eric可能会纠正我的错误,但所谓的基元类型不是主要与C#编译器(比方说编译器sugar)相关,而不是CTS、CLR或CIL?任何类型都可以分解为对象(但这肯定是另一个要讨论的话题)。但是编译器(或者我将如何)从不同的可能的编码方式中进行选择呢?ldnull
,ldfalse
,ldc…..0
看起来很明显……但是为什么initobj
?简单,null
是一个(值)类型对象,因此使用initobj
初始化它。通过消除来考虑它:您不能将整数加载到它,值类型不能为null(甚至不是null
类型),所以剩下的就是将它初始化为空(默认构造函数)对象。string
行为不同的原因是它不是一个值类型,而是一个类。它可以保存一个空引用,所以default(string)
解析为null
,就像default(Form)一样
例如。请注意,我的CreateClassDefault
和CreateDefaultNull
emitinitobj
也…尽管有where T:class
约束!我不确定。我尝试了class
和class,new()
作为发布模式中的约束,它仍然使用initobj
。乍一看,它应该只加载null
。也许Eric可以给你一个更好的答案,因为他看到了你的帖子!我不明白,为什么要在引用类型(t:class
)中加载null
)给出一个关于unboxT
?@dasblinkenlight:default(int?
不是编译时常量。default(Guid)
不是编译时常量。任何非内置值类型都不是常量。@Blindy:CLI对“boxing”的定义不同于C对“boxing”的定义;记住,它们有不同的类型系统。这是意料之中的;CLI类型系统的级别低于C#type系统。有关装箱的CLI定义的简要概述,请参阅第I部分第8.2.4节。@stakx:重要部分分散在整个规范中,但基本上验证器使用一些相当复杂的规则。这些规则区分类型参数的装箱类型和未装箱类型,并且这些规则没有考虑“类”约束静态证明运行时装箱类型将与运行时未装箱类型相同的事实。基本上,所讨论的程序既有效又类型安全,但是rifier过于简单化,无法实现这一点。@stakx:是和否。使用泛型是使事情真正通用的唯一方法。如果不使用泛型,而是使用特定类型,编译器通常会发出initobj
用于非“内置”值类型,发出ldnull
用于引用类型,发出特定代码用于“内置”值类型。
... etc
.class private auto ansi beforefieldinit P
extends [mscorlib]System.Object
{
.method private hidebysig static !!T M<class T>() cil managed
{
.maxstack 1
ldnull
ret
}
... etc
D:\>peverify foo.exe
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.17379
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error:
[d:\foo.exe : P::M[T]]
[offset 0x00000001]
[found Nullobjref 'NullReference']
[expected (unboxed) 'T']
Unexpected type on the stack.
1 Error(s) Verifying d:\foo.exe