调用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 或 DateTime 设置为 null,但我们不能,因为它们不是引用其他内容的类型,因此不能在不提及任何事物的状态,就像我作为哺乳动物一样会失去我的羽毛。
2.0 引入的可空类型通过与引用类型不同的机制,为我们提供了一种具有语义 null 的值类型。如果不存在,大部分内容您可以自己编写代码,但特殊的装箱和提升规则允许更明智的装箱和操作员使用。
好的。现在让我们考虑一下为什么NullReferenceExceptions 首先会发生。两个是不可避免的,一个是 C# 中的设计决定(并不适用于所有 .NET)。
- 您尝试调用虚拟方法或属性,或访问空引用上的字段。这必须失败,因为没有办法查找应该调用什么覆盖,也没有这样的字段。
- 您在空引用上调用非虚拟方法或属性,而该引用又调用虚拟方法或属性,或访问字段。这显然是第一点的变体,但我们接下来要进行的设计决策的优势是保证它在一开始就失败,而不是部分完成(这可能会令人困惑并有长期的副作用) .
- 您在不调用虚拟方法或属性的空引用上调用非虚拟方法或属性,或访问字段。没有内在的理由不允许这样做,并且某些语言允许这样做,但是在 C# 中,他们决定使用
callvirt 而不是 call 来强制使用 NullReferenceException 以保持一致性(不能说我同意,但是你去)。
这些情况都不适用于可空值类型。不可能将可空值类型放入无法知道要访问哪个字段或方法覆盖的条件中。 NullReferenceException 的整个概念在这里没有意义。
总之,不抛出 NullReferenceException 是与其他类型一致 - 当且仅当使用空引用时才通过它进行类型。
请注意,有时调用 null 可空类型会抛出异常,GetType() 会抛出这种情况,因为 GetType() 不是虚拟的,并且在调用值类型时总是会隐含装箱。其他值类型也是如此:
(1).GetType()
被视为:
((object)1).GetType()
但在可空类型的情况下,装箱会将那些带有错误HasValue 的类型变为空,因此:
default(int?).GetType()
被视为:
((object)default(int?)).GetType()
这导致GetType() 在一个空对象上被调用,并因此被抛出。
这顺便让我们明白了为什么不伪装NullReferenceType 是更明智的设计决策——需要这种行为的人总是可以装箱。如果您希望它通过,请使用((object)myNullableValue).GetString(),因此语言无需将其视为特殊情况来强制异常。
编辑
哦,我忘了提到NullReferenceException 背后的机制。
NullReferenceException 的测试非常便宜,因为它大多只是忽略问题,然后在发生异常时从操作系统捕获异常。换句话说,没有测试。
请参阅 What is the CLR implementation behind raising/generating a null reference exception? 并注意这些都不适用于可空值类型。