调用引发的异常default(object).ToString()
是NullReferenceException
有原因的,它在空引用上调用方法。default(int?)
另一方面,不是空引用,因为它不是引用;它是一个值类型,其值等同于 null。
最大的实际问题是,如果这样做了,那么以下操作将失败:
default(int?).HasValue // should return false, or throw an exception?
它还会破坏我们混合可空值和不可空值的能力:
((int?)null).Equals(1) // should return false, or throw an exception?
以下变得完全无用:
default(int?).GetValueOrDefault(-1);
我们可以摆脱HasValue
并强制与 null 进行比较,但是如果在某些情况下与 null 比较时,可以为 null 的值类型的相等覆盖可以返回 true 怎么办。这可能不是一个好主意,但它可以做到,并且语言必须应对。
让我们回想一下为什么要引入可空类型。引用类型可以为 null 的可能性是引用类型概念中固有的,除非努力强制执行不可为空性:引用类型是引用某事物的类型,这意味着一个人不引用任何事物的可能性,这我们称之为null。
虽然在许多情况下很麻烦,但我们可以在各种情况下使用它,例如表示“未知值”、“没有有效值”等(例如,我们可以将它用于null在数据库中的含义) .
在这一点上,我们已经在给定的上下文中赋予了null一个含义,而不仅仅是给定的引用不引用任何对象的简单事实。
由于这很有用,因此我们可能希望将int
or设置DateTime
为 null,但我们不能,因为它们不是引用其他内容的类型,因此不能处于不引用任何内容的状态作为哺乳动物,我可能会失去我的羽毛。
2.0 引入的可空类型通过与引用类型不同的机制为我们提供了一种可以具有语义null的值类型。如果它不存在,您可以自己编写大部分代码,但特殊的装箱和升级规则允许更明智的装箱和操作员使用。
好的。现在让我们首先考虑一下为什么NullReferenceExceptions
会发生。两个是不可避免的,一个是 C# 中的设计决定(并不适用于所有 .NET)。
- 您尝试调用虚拟方法或属性,或访问空引用上的字段。这必须失败,因为没有办法查找应该调用什么覆盖,也没有这样的字段。
- 您在空引用上调用非虚拟方法或属性,该引用又调用虚拟方法或属性,或访问字段。这显然是第一点的变体,但我们接下来要进行的设计决策的优点是保证它在一开始就失败,而不是部分完成(这可能会令人困惑并有长期的副作用) .
- 您在不调用虚拟方法或属性的空引用上调用非虚拟方法或属性,或访问字段。没有内在的理由不应该允许这样做,并且某些语言允许这样做,但是在 C# 中,他们决定使用
callvirt
而不是call
强制 aNullReferenceException
以保持一致性(不能说我同意,但你去吧)。
这些情况都不适用于可空值类型。不可能将可空值类型放入无法知道要访问哪个字段或方法覆盖的条件中。just的整个概念在NullReferenceException
这里没有意义。
总之,不抛出 aNullReferenceException
与其他类型一致——当且仅当使用空引用时,通过它的类型。
请注意,在调用可空类型为 null 的情况下,它会抛出 ,GetType()
因为GetType()
它不是虚拟的,并且当调用值类型时,总是有一个隐含的装箱。其他值类型也是如此,因此:
(1).GetType()
被视为:
((object)1).GetType()
但在可空类型的情况下,装箱会将带有 false 的类型HasValue
转换为空,因此:
default(int?).GetType()
被视为:
((object)default(int?)).GetType()
这导致在GetType()
一个空对象上被调用,因此抛出。
这顺便让我们明白了为什么不伪装NullReferenceType
是更明智的设计决策——需要这种行为的人总是可以装箱。如果您希望它通过然后使用((object)myNullableValue).GetString()
,那么语言无需将其视为特殊情况来强制异常。
编辑
哦,我忘了说背后的机制NullReferenceException
。
测试NullReferenceException
非常便宜,因为它通常只是忽略问题,然后在发生异常时从操作系统捕获异常。换句话说,没有 test。
请参阅引发/生成空引用异常背后的 CLR 实现是什么?并注意这些都不适用于可空值类型。