在.Net / C#中,是否为强类型?

时间:2009-08-12 11:25:42

标签: c# .net

null是否有类型?如何在内部表示空值?以下代码中发生了什么?

void Foo(string bar) {...}
void Foo(object bar) {...}

Foo((string)null);

编辑:迄今为止的答案都是非特定的,而且太高级了。我知道引用类型对象由堆栈上的指针组成,指针指向堆上的一个位置,该位置包含同步块索引,类型句柄和对象的字段。当我将对象的实例设置为null时,堆栈上的指针指向哪里?在代码片段中,C#编译器只是简单地使用它来决定调用哪个重载,并且没有真的任何一个null的转换?

我正在寻找了解CLR内部人员的深入答案。

7 个答案:

答案 0 :(得分:17)

代码示例中对string的强制转换不会为null提供类型,因为null本身不能拥有类型。如果您需要证明这一点,那么执行以下代码,您可以看到null总是等于它自己,无论它被分配给变量的类型是什么:

string s = null;
IPAddress i = null;
Console.WriteLine(object.Equals(s, i)); // prints "True"
Console.WriteLine(object.ReferenceEquals(s, i)); // prints "True"

强制转换的作用是告诉编译器选择哪个重载。由于null没有类型,因此不知道是否选择带有objectstring的重载,因为该值可以解释为。所以你要通过说“这是一个应该被视为字符串的空值”来帮助它。


如果你想看看下面发生了什么,那么从代码中查看IL。方法调用的相关位在文本IL中类似于以下内容(取决于您的命名空间和类名等):

ldnull 
call void ConsoleApplication1.Program::Foo(string)

所有发生的事情是在堆栈上加载null,然后由占用字符串的重载消耗,因为在编译时执行重载解析,因此调用的方法被烘焙到IL中。

如果你想看看ldnull做了什么,以及为什么它与仅使用像ldc.i4.0这样的东西将零加载到堆栈上有所不同,那么请看this answer(如果你没有想要关注链接,原因是它是一个与大小无关的零,否则在CLR中不存在。

答案 1 :(得分:6)

null没有类型,并且“在代码片段中,C#编译器只是使用它来决定调用哪个重载,并且实际上没有任何转换为​​null?”正是这样。让我们看一下生成的IL。

  IL_0001:  ldnull
  IL_0002:  call       void ConsoleApplication1.Program::Foo(string)

注意ldnull向堆栈加载null。它只是null,不是作为字符串或其他东西的null。重要的是第二行,其中IL显式调用接收字符串的重载。如果你打电话给对象,那么会发生以下情况:

  IL_0001:  ldnull
  IL_0002:  call       void ConsoleApplication1.Program::Foo(object)

所以,是的,强制转换是一个C#工件,因此编译器知道要调用的重载。

答案 2 :(得分:2)

它将调用Foo(字符串栏)。

你可以将null罚款。

答案 3 :(得分:2)

当您将引用对象设置为null时,指针(后面)指向内存中指定为null的特殊位置。 因此当你转向null时,你真正创建了一个指向null的特定类型的指针。

正如其他人(1) (2)所证明的那样,情况似乎并非如此,因为编译器根据特定的null强制转换来解决发出IL的重载。

我知道引用变量有一个与之关联的类型。我认为这将是null投射的背后,但我错了。

ldnull操作码将空引用(类型O)压入堆栈。并且,即使此null引用具有与之关联的Type,call也会将此引用接受为string的有效参数。至少,这是我对它的理解。如果有人对此有任何更正,请随时纠正我。

答案 4 :(得分:1)

null关键字是一个表示空引用的文字,一个不引用任何对象的引用。 null是引用类型变量的默认值。来自MSDN。所以你的例子中字符串的默认值为。

// A null string is not the same as an empty string.
string s = null;
string t = String.Empty; // Logically the same as ""

您所做的相当于按照上述使用默认值:

int equal = string.Compare((string)null, default(string));

答案 5 :(得分:1)

NULL是一个标记,它没有数据类型。当您将.NULL分配给字段或变量时,该值将更改为NULL,但字段或变量的数据类型不会更改。

我认为在你的例子中将字符串类型赋值为null的原因是为了显示将使用witch方法(在这种情况下为“void Foo(string bar){...}”,因为这是接受的方法字符串作为参数。)

如果你要调用Foo((object)null);将使用另一种方法。(“void Foo(object bar){...}”)

答案 6 :(得分:1)

null值的类型由ECMA-334定义如下:

  

11.2.7空类型

     

null literal(第9.4.4.6节)求值为null   value,用于表示a   引用没有指向任何对象   或数组,或没有值。   null类型有一个值,   这是空值。因此一个   类型为null类型的表达式   只能评估空值。   没有办法明确写   null类型,因此没办法   在声明的类型中使用它。

null类型是类型层次结构的底部类型 - 与对象相反; null类型可以被认为是每个可空类型的子类型,因为null值可以在任何可以为空的表达式出现的地方使用。

这确实在类型系统中创建了一个弱点,因为它意味着接收者是可空类型的任何操作都可以为null,并且操作可能在运行时因异常而失败。在强类型系统中,如果接收器属于该类型,则保证由类型提供的操作(即,您不会获得空指针异常,这实际上是说null类型没有实现您所使用的任何方法在它上面调用,虽然null值可以来自你认为你正在使用的任何类型的表达式)。

在您的代码中,方法被重载,并且产生参数的表达式的静态类型用于重载解析。由于强制转换为字符串,表达式具有类型字符串(null类型是字符串的子类型,根据C#类型系统,这是一个上传,所以是安全的)。作为第一个近似值,编译器选择最具体的重载可见,因此选择Foo的字符串版本而不是对象版本。