为什么编译器让我在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
是一个types ,E
是一个一元expression式 )的一个强制转换expression式 ,将E
的值显式转换(第6.2节)为T
types。 [… 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值。