为什么编译器让我在C#中将特定的types转换为null?

考虑这个代码:

var str = (string)null; 

当写代码这是我的IL代码:

 IL_0001: ldnull 

IL有Cast操作符,但是:

 var test = (string) new Object(); 

IL代码是:

 IL_0008: castclass [mscorlib]System.String 

所以将null强制转换为string被忽略。

为什么编译器让我把null成特定的types?

在这个级别的IL中, null就是null 。 编译器知道它是null因为这是你写的,所以编译器根本不需要调用cast操作符。 将null转换为对象只会产生null

所以如果你愿意,这是一个编译时“优化”或简化。

由于这是合法的,为了将null为另一个对象types,既没有警告也没有错误报告。

请注意,编译器显然不会这样做,甚至认为它可能能够validation被投射的值确实保证为null ,如果它不是一个文字。

你的例子:

 void Main() { var s = (string)null; GC.KeepAlive(s); } 

IL:

 IL_0000: ldnull IL_0001: stloc.0 // s IL_0002: ldloc.0 // s IL_0003: call System.GC.KeepAlive 

(我添加了对GC.KeepAlive的调用,以避免编译器因为没有在任何地方使用而丢弃整个variables。)

如果我先将null填充到一个对象中,但不可能改变它:

 void Main() { object o = null; var s = (string)o; GC.KeepAlive(s); } 

IL:

 IL_0000: ldnull IL_0001: stloc.0 // o IL_0002: ldloc.0 // o IL_0003: castclass System.String IL_0008: stloc.1 // s IL_0009: ldloc.1 // s IL_000A: call System.GC.KeepAlive 

在Java中,至less有一种情况需要将null为某种types,也就是说,在使用重载方法告诉编译器要执行哪种方法时(我假设C#也是这种情况)。 由于null值为0 (或者null表示的任何指针),无论是什么types,你都不会在编译代码中看到任何差异(除了调用哪个方法外)。

因为规范是这样说的。 请参阅C#5标准的第6.1.5节,第6.2节和第7.7.6节。 只引用相关部分:

§7.7.6转换expression式

(T)E (其中T是一个typesE是一个一元expression式 )的一个强制转换expression式 ,将E的值显式转换(第6.2节)为Ttypes。 [… T]他的结果是显式转换产生的价值。

§6.2显式转换

以下转换被分类为显式转换:

  • 所有隐式转换。

§6.1.5隐式引用转换

隐含的参考转换是:

  • 从null文字到任何引用types

铸造一个null是完全有效的 – 有时当为了通知编译器调用哪个方法而将parameter passing给重载的方法时,它是必需的。

请参阅以下相关问题:

将null作为对象投射?

强制转换的意义在于将str定义为Stringtypes,所以关于是否可以将null作为types进行强制转换,而不是定义variables的types。

不是所有看起来像(SomeType)expression都是C#中的一个类。 有时expression式需要获取它没有的types。 其他一些例子:

 var a = (short)42; var b = (Func<int, int>)(i => i + 1); var c = (IConvertible)"Hello"; 

在每一种情况下,如果你喜欢的话,也可以在左边写上这个types,而不是var 。 但是,当expression式是更大的expression的一部分时,情况并非总是如此:

 CallOverloadedMethod((short)42); // CallOverloadedMethod has another overload that we don't want var y = b ? x : (IConvertible)"Hello"; // the ?: operator might not be able to figure out the type without this hint 

在你的例子中,文字null本身没有types。 在某些情况下,比如在使用三元操作符的时候,或者在用var语法声明一个variables的时候,就像在重载之间进行select一样,必须有一个expression式仍然是null但是也要携带一个types。

请注意,重载也包括操作员,例如:

 public static bool operator ==(Giraffe g1, Giraffe g2) { if (g1 == (object)null && g2 != (object)null || g1 != (object)null && g2 == (object)null) { return false; } // rest of operator body here ... } 

(object)null语法用于确保用户定义的==重载不是recursion调用的。

你的语法是正确的,在C#中没有规范限制。 这些是规范规则:

隐含的参考转换是:

  • 从任何参考types到对象和dynamic。

  • 从任何typesS到任何typesT,只要S是从T派生的。

  • 从任何typesS到任何接口typesT,只要S实现T.

  • 从任何接口typesS到任何接口typesT,如果S是从T派生的。

  • 从元素typesSE的数组typesS到元素types为TE的数组typesT,只要满足以下条件即可:o S和T仅在元素types上有所不同。 换句话说,S和T具有相同的维数。 SE和TE都是参考types。 存在从SE到TE的隐式参考转换。

  • 从任何数组types到System.Array及其实现的接口。

  • 从一维数组typesS []到System.Collections.Generic.IList及其基本接口,只要存在从S到T的隐式标识或引用转换。

  • 从任何委托types到System.Delegate及其实现的接口。

  • 从null文字到任何引用types。

  • 从任何引用types到引用typesT,如果它具有隐式标识或引用转换为引用typesT0,并且T0具有到T的标识转换。

  • 从任何引用types到接口或委托typesT(如果它具有到接口或委托types的隐式标识或引用转换),T0和T0是方差可变的(第13.1.3.2节)到T.

  • 涉及已知为引用types的types参数的隐式转换。 有关涉及types参数的隐式转换的更多细节,请参阅第6.1.10节。 隐式引用转换是那些可以被certificate总是成功的引用types之间的转换,因此在运行时不需要检查。 引用转换(隐式或显式)不会更改正在转换的对象的引用标识。 换句话说,虽然引用转换可能会改变引用的types,但它永远不会改变被引用的对象的types或值。

假设编译器确实返回了一个警告var str = (string)null; 。 所以应该警告这条线: var str = SomeFunctionThatReturnsNull()当使用var编译器时,必须知道它应该在编译时初始化的types,也不要紧,如果你打算把一个null值作为它只要它被声明为可空types。 看到编译器没有在空的情况下调用Cast是没有意外的,因为没有什么可以被转换的。

我认为你应该阅读说明书。你可以为每个你想要的空值。 看见:

•从null文字到任何引用types。

就在使用之前你检查null值。