如何翻译;默认值(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
emit
initobj
也…尽管有
where T:class
约束!我不确定。我尝试了
class
class,new()
作为发布模式中的约束,它仍然使用
initobj
。乍一看,它应该只加载
null
。也许Eric可以给你一个更好的答案,因为他看到了你的帖子!我不明白,为什么要在引用类型(
t:class
)中加载
null
)给出一个关于unbox
T
?@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