21

有人可以解释一下为什么调用ToString()空引用类型会导致异常(在我看来这很有意义,你不能什么都不调用方法!)但是调用ToString()Nullable(Of T)返回String.Empty?这让我很惊讶,因为我认为这种行为在不同类型中是一致的。

Nullable<Guid> value = null;
Stock stock = null;
string result = value.ToString(); //Returns empty string
string result1 = stock.ToString(); //Causes a NullReferenceException
4

6 回答 6

20

Nullable<T>实际上是 a struct,它具有一些编译器支持和实现支持以表现得像 anull而实际上不是null.

您看到的是实现之间的冲突,允许您像对待null任何其他引用类型一样自然地将其视为 a,但允许发生方法调用,因为Nullable<T>它实际上不是 null,它内部的值是 null。

从视觉上看,它看起来不应该工作,这仅仅是因为你看不到后台为你做了什么。

当您在空引用类型上调用扩展方法时,可以看到其他此类视觉技巧......该调用有效(违背视觉预期),因为在引擎盖下它被解析为静态方法调用,将您的空实例作为参数传递。

Nullable<T> 类型如何在幕后工作?

于 2012-08-03T07:59:00.183 回答
6

Nullable 是一个值类型,对它的赋值null导致它被初始化为Value=nulland HasValue=false

此外, Nullable.ToString() 实现如下:

public override string ToString()
{
    if (!this.HasValue)
    {
        return "";
    }
    return this.value.ToString();
}

所以你所看到的是预期的。

于 2012-08-03T07:55:18.523 回答
4

可空类型有点棘手。当您将其设置为null它实际上不是null因为它不是引用类型(它是值类型)。当你用它初始化这样的变量时,null它会创建新的结构实例,其中HasValue属性是false并且它Valuenull,所以当你调用ToString方法时,它在结构实例上运行良好。

于 2012-08-03T07:55:27.117 回答
1

调用引发的异常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一个含义,而不仅仅是给定的引用不引用任何对象的简单事实。

由于这很有用,因此我们可能希望将intor设置DateTime为 null,但我们不能,因为它们不是引用其他内容的类型,因此不能处于不引用任何内容的状态作为哺乳动物,我可能会失去我的羽毛。

2.0 引入的可空类型通过与引用类型不同的机制为我们提供了一种可以具有语义null的值类型。如果它不存在,您可以自己编写大部分代码,但特殊的装箱和升级规则允许更明智的装箱和操作员使用。

好的。现在让我们首先考虑一下为什么NullReferenceExceptions会发生。两个是不可避免的,一个是 C# 中的设计决定(并不适用于所有 .NET)。

  1. 您尝试调用虚拟方法或属性,或访问空引用上的字段。这必须失败,因为没有办法查找应该调用什么覆盖,也没有这样的字段。
  2. 您在空引用上调用非虚拟方法或属性,该引用又调用虚拟方法或属性,或访问字段。这显然是第一点的变体,但我们接下来要进行的设计决策的优点是保证它在一开始就失败,而不是部分完成(这可能会令人困惑并有长期的副作用) .
  3. 您在不调用虚拟方法或属性的空引用上调用非虚拟方法或属性,或访问字段。没有内在的理由不应该允许这样做,并且某些语言允许这样做,但是在 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 实现是什么?并注意这些都不适用于可空值类型。

于 2012-08-03T11:26:33.167 回答
0

如果您调查Nullable<>定义,则会有一个覆盖 ToString 定义。在此函数中,ToString 被覆盖以返回 String.Empty。

    // Summary:
    //     Returns the text representation of the value of the current System.Nullable<T>
    //     object.
    //
    // Returns:
    //     The text representation of the value of the current System.Nullable<T> object
    //     if the System.Nullable<T>.HasValue property is true, or an empty string ("")
    //     if the System.Nullable<T>.HasValue property is false.
    public override string ToString();

另一方面, Stock 是一个自定义类,我假设 ToString 没有被覆盖。因此它返回 NullReferenceException 因为它使用默认行为。

于 2012-08-03T07:58:09.003 回答
0

根据 MSDN 备注

Guid.ToSTring()方法 根据提供的格式说明符,返回此 Guid 实例值的字符串表示形式。

根据 MSDN Remarks on Nullable

如果一个类型可以被赋值或可以被赋值为 null,则称该类型可空,这意味着该类型没有任何值。因此,可空类型可以表示一个值,或者不存在值。例如,String 等引用类型可以为空,而 Int32 等值类型则不能。值类型不能为空,因为它有足够的能力只表达适合该类型的值;它没有表达 null 值所需的额外容量。

于 2012-08-03T08:04:32.287 回答