为什么编译器允许我在C#中将null转换为特定类型?

时间:2014-01-05 11:45:12

标签: c# casting null il

考虑以下代码:

var str = (string)null;

编写代码时,这是我的IL代码:

IL_0001:  ldnull

IL有任何Cast操作符,但是:

var test = (string) new Object();

IL代码为:

IL_0008:  castclass  [mscorlib]System.String

因此忽略了nullstring的投射。

为什么编译器允许我将null转换为特定类型?

9 个答案:

答案 0 :(得分:47)

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

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

由于这是合法的,要将null转换为其他对象类型,既没有警告也没有报告错误。

请注意,显然编译器不会这样做,即使它可能能够验证正在投射的值确实是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的调用,以避免编译器丢弃整个变量,因为它没有在任何地方使用。)

如果我先将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

答案 1 :(得分:19)

在Java中,至少有一种情况需要将null转换为某种类型,这就是使用重载方法告诉编译器要执行哪种方法(我假设这是在C#中也是如此)。由于null0(或任何指针null代表),无论它是什么类型,你都不会看到编译代码的任何差异(除了哪种方法是叫)。

答案 2 :(得分:13)

因为规范是这样说的。参见C# 5 standard的§6.1.5,§6.2和§7.7.6。仅引用相关部分:

  

§7.7.6演员表达

     

(T)E形式的演员表达,其中T类型E unary-expression ,对E的值执行显式转换(第6.2节)以键入T。 [... T]他的结果是显式转换产生的值。

     

§6.2明确转换

     

以下转化归类为显式转化:

     
      
  • 所有隐式转化。
  •   
     

§6.1.5隐式参考转化

     

隐式参考转换是:

     
      
  • 从null文字到任何引用类型
  •   

答案 3 :(得分:8)

抛出null是完全有效的 - 有时在将参数传递给重载方法时是必需的,以便通知编译器正在调用哪个方法。

请参阅以下相关问题:

Casting null as an object?

答案 4 :(得分:4)

强制转换的目的是将str定义为String类型,因此它不是关于是否可以将null转换为Type,而是关于定义变量类型的更多信息。

答案 5 :(得分:4)

在C#中,并非所有看起来像(SomeType)expression的内容都是强制转换。有时候表达式需要获得一个它已经没有的类型。其他一些例子:

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

在每种情况下,如果有人愿意,也可以在左边写一个类型,而不是var。但是当表达式是更大表达式的一部分时,情况并非总是如此,例如:

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 本身没有类型。在某些情况下,比如在多次重载之间进行选择时,例如在使用三元?:运算符时,或者在使用var语法声明变量时,必须使表达式仍为{{1}但也带有一种类型。

请注意,重载还包括运算符,例如:

null

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的用户定义重载。

答案 6 :(得分:2)

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

  

隐式参考转换是:

     
      
  • 从任何引用类型到对象和动态。

  •   
  • 从任何类型S到任何类型类型T,只要S是派生的   来自T。

  •   
  • 从任何类型S到任何接口类型T,只要S实现   吨。

  •   
  • 从任何接口类型S到任何接口类型T,只要S是   源自T。

  •   
  • 从具有元素类型SE的数组类型S到具有的数组类型T.   如果满足以下所有条件,则为元素类型TE:o S和T.   仅在元素类型上有所不同。换句话说,S和T具有相同的含义   尺寸数量。 o SE和TE都是参考类型。 o   隐式参考转换存在于SE到TE。

  •   
  • 从任何数组类型到System.Array及其接口   器具。

  •   
  • 从一维数组类型S []到   提供了System.Collections.Generic.IList及其基接口   从S到S的隐式标识或引用转换   吨。

  •   
  • 从任何委托类型到System.Delegate及其接口   器具。

  •   
  • 从null文字到任何参考类型。

  •   
  • 从任何引用类型到引用类型T(如果它具有隐式)   身份或参考转换为参考类型T0和T0有一个   身份转换为T。

  •   
  • 从任何引用类型到接口或委托类型T(如果有)   隐式身份或引用转换为接口或   委托类型T0和T0是方差可转换(第13.1.3.2节)到T。

  •   
  • 涉及已知类型参数的隐式转换   参考类型。有关隐式转换的更多详细信息,请参见第6.1.10节   涉及类型参数。隐式引用转换是   引用类型之间的转换,可以始终证明   成功,因此在运行时不需要检查。参考   转换,隐式或显式,永远不会更改引用   被转换对象的身份。换句话说,而一个   引用转换可能会改变引用的类型,它永远不会   更改所引用对象的类型或值。

  •   

答案 7 :(得分:1)

假设编译器确实为var str = (string)null;返回警告。那么这条线应该被警告吗?:var str = SomeFunctionThatReturnsNull()虽然使用var编译器必须知道它应该在编译时初始化的类型,并且如果你要在其中放置一个空值也没关系因为它被声明为可以为空的类型。看到编译器没有调用Cast in null的情况就不足为奇了,因为没有任何东西可以被强制转换。

答案 8 :(得分:0)

我认为你应该阅读规范。你可以为你想要的每个人施放一个空值。 看到它:

  

•从null文字到任何引用类型。

在使用之前,您检查的值为null。