引用类型与可为 Null 的类型 ToString()

Reference types vs Nullable types ToString()

提问人:Maxim Gershkovich 提问时间:8/3/2012 更新时间:8/4/2012 访问量:2313

问:

有人可以解释为什么调用空引用类型会导致异常(在我看来这是完全有道理的,你不能调用一个方法!)但是调用一个空的返回?这让我感到非常惊讶,因为我认为不同类型的行为是一致的。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
C# 引用类型

评论


答:

6赞 logicnp 8/3/2012 #1

Nullable 是一种值类型,赋值会导致它使用 和 进行初始化。nullValue=nullHasValue=false

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

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

所以你所看到的是意料之中的。

评论

0赞 8/3/2012
我认为问题更多的是“由于值为空,如何调用该 ToString?
0赞 Mike Marynowski 12/2/2012
值不会初始化为 null,而是初始化为 default(T)。value 是值类型,不能为 null。实际上 Value 是一个属性,因此它不会初始化为任何内容,但它的支持字段会初始化为 default(T),尽管您永远不会看到该值,因为该属性会引发异常。
4赞 user854301 8/3/2012 #2

对于可为 null 的类型,这有点棘手。当你把它设置为它时,它实际上不是因为它不是引用类型(它是值类型)。当你用它初始化这样的变量时,它会创建一个新的结构实例,其中 property is 和 it's is ,所以当你调用方法时,它在结构实例上工作得很好。nullnullnullHasValuefalseValuenullToString

评论

0赞 Jon Hanna 8/3/2012
它实际上是空的。null 和 nothingness 的概念早于计算中的引用概念。我们可以通过一个不引用的引用来表示这个概念,这不是唯一可能的方法。可为 null 的值类型是另一种。
0赞 Daniel Pryden 8/4/2012
@JonHanna:你说对了一半。诚然,无效性的概念通常早于引用的概念。但是 user854301 在这里说,当您在 C# 中将可为 null 的类型设置为它时,它实际上并不是 - 这是 100% 正确的。该词(尤其是用代码字体书写时)在 C# 的上下文中具有特定的含义,它与一般的 nullity 概念是分开的(尽管与之相关)。这就像说它是一个整数-- 它当然,以一般的存在主义方式,但从语言的角度来看,它是一个整数。nullnullnull42.0double
0赞 Jon Hanna 8/4/2012
@DanielPryden如果我们这样做,我们已将 null 分配给 .如果我们测试,那么我们会得到 .就 C# 的含义而言,是 .但是,它不是 null 引用,因为虽然它是 null,但它不是引用double? d = null;ddouble == nulltruenulldnull
0赞 user854301 8/5/2012
Equals 方法是可为 null 类型的重写。所有这些东西都是为了支持与db(观点)的通信而设计的,而不是简单地添加“另一种类型”。
0赞 Jon Hanna 8/7/2012
@user854301数据库工作远非它们的唯一用途(数据库有空是有原因的,这个原因适用于其他地方)。
0赞 daryal 8/3/2012 #3

如果调查定义,则会重写 ToString 定义。在此函数中,重写 ToString 以返回 String.Empty。Nullable<>

    // 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,因为它使用默认行为。

评论

1赞 phoog 8/3/2012
这个答案忽略了一个关键事实,即 Nullable 是一种值类型。对任何引用类型调用 ToString 都会引发 NullReferenceException,无论该类型是否具有 ToString 重写。
0赞 daryal 8/3/2012
@phoog 此答案不直接或间接地暗示与引用类型相关的任何内容。对不起,如果我不明白这一点。我还说过,如果它是“一个类”并且值为“null”;则默认行为返回 NullReferenceException。
0赞 phoog 8/28/2012
您的回答意味着向类添加重写会改变程序的行为,但这是不正确的。NullReferenceException 与 because 是引用类型一起发生,而 because 是值类型时不会发生。重写的存在与否与是否引发 NullReferenceException 无关。换句话说,答案的问题恰恰在于它没有提到值类型与引用类型的问题。ToString()StockStockStockNullable<Guid>Nullable<>ToString()
20赞 Adam Houldsworth 8/3/2012 #4

Nullable<T>实际上是一个具有一些编译器支持和实现支持的行为,但实际上却没有。structnullnull

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

从视觉上看,它似乎不应该起作用,这仅仅是因为您看不到在后台为您完成的工作。

当您对 null 引用类型调用扩展方法时,可以看到其他此类视觉技巧......该调用有效(与视觉预期相反),因为在后台,它被解析为静态方法调用,将 null 实例作为参数传递。

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

评论

0赞 Jon Hanna 8/3/2012
取决于您的视觉期望。有人会说,事实上,调用不引用任何字段或虚拟方法的非虚拟方法确实会引发一个违背预期的异常,因为除了 C# 强制执行之外,没有真正的原因(但 .NET 不会强制执行此操作)。
0赞 Adam Houldsworth 8/3/2012
@JonHanna 不是真的,我希望它失败,因为它看起来像一个实例方法调用,而不管它可以毫无问题地重新定义为静态。但我明白你的观点,即期望会因经验而受到玷污。在OP的情况下,我认为它站得住脚。
0赞 Jon Hanna 8/3/2012
是的,但我不希望它失败,因为我不希望失败。它没有理由失败,但 C# 付出了额外的努力来让它失败,而其他语言则不会。由于我不希望它作为非扩展方法失败,所以我也不期望它作为扩展方法失败,直到我了解 C# 有一个特殊情况,即对 null 对象的方法进行安全调用。class A{public void DoNothing(){}};/*...*/(A(null)).DoNothing();
1赞 Adam Houldsworth 8/3/2012
@JonHanna 你的意思是从其他语言中学习?在我看来,尝试在空项上调用实例成员似乎很自然,无论其实现如何,都会导致问题。我的主要语言经验来自 C#,根据 OP 在问题中使用的语言,我仍然认为我的术语是成立的。我最初也认为他们没有付出额外的努力让它失败,他们只是没有付出额外的努力来让它根据实际工作。DoSomething
1赞 Adam Houldsworth 8/3/2012
@MAfifi Null 赋值不是来自实现 ()。除此之外,我不太确定,我所知道的只是编译器芯片允许它做它所做的事情。Nullable<bool> b = null;
0赞 HatSoft 8/3/2012 #5

根据 MSDN 备注

Guid.ToSTring() 方法 返回 此 Guid 实例的值,根据提供的格式 规范。

根据 MSDN 对 Nullable 的备注

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

评论

0赞 HatSoft 8/3/2012
请阅读我的完整答案,当您阅读它时,它是不完整的
1赞 Jon Hanna 8/3/2012 #6

调用引发的异常是有原因的,它正在调用对 null 引用的方法。 另一方面,不是 null 引用,因为它不是引用;它是一种值类型,其值等效于 null。default(object).ToString()NullReferenceExceptiondefault(int?)

最大的实际要点是,如果这样做,那么以下操作将失败:

default(int?).HasValue // should return false, or throw an exception?

它还会搞砸我们混合 null 和 non-nullable 的能力:

((int?)null).Equals(1) // should return false, or throw an exception?

以下内容变得完全无用:

default(int?).GetValueOrDefault(-1);

我们可以摆脱并强制与 null 进行比较,但是如果在某些情况下与 null 相比,使 null 的值类型的等式覆盖可以返回 true,该怎么办?这可能不是一个好主意,但它是可以做到的,语言必须应对。HasValue

让我们回想一下为什么引入可为 null 的类型。引用类型可以为 null 的可能性是引用类型概念中固有的,除非努力强制执行非 null 性:引用类型是引用某物的类型,这意味着不引用任何事物的可能性,我们称之为 null

虽然在许多情况下很麻烦,但我们可以在各种情况下使用它,例如表示“未知值”、“无有效值”等(例如,我们可以将其用于数据库中的 null 含义)。

在这一点上,我们已经在给定的上下文中赋予了 null 一个含义,而不仅仅是给定引用不引用任何对象的简单事实。

由于这很有用,因此我们可以想要将 or 设置为 null,但我们不能,因为它们不是引用其他事物的类型,因此不能处于不引用任何事物的状态,就像我作为哺乳动物可以失去羽毛一样。intDateTime

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

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

  1. 您尝试调用虚拟方法或属性,或访问空引用上的字段。这必须失败,因为没有办法查找应该调用什么覆盖,也没有这样的字段。
  2. 对 null 引用调用非虚拟方法或属性,而 null 引用又调用虚拟方法或属性,或访问字段。这显然是第一点的一个变体,但我们接下来要讨论的设计决策的优点是保证这在一开始就失败,而不是中途失败(这可能会令人困惑并产生长期副作用)。
  3. 对空引用调用非虚拟方法或属性,该引用不调用虚拟方法或属性,也不访问字段。没有内在的理由不应该允许这样做,有些语言允许这样做,但在 C# 中,为了一致性,他们决定使用而不是强制使用(不能说我同意,但你去吧)。callvirtcallNullReferenceException

这些情况都不适用于可为 null 的值类型。不可能将可为 null 的值类型放入无法知道要访问的字段或方法重写的条件中。“只是”的整个概念在这里没有意义。NullReferenceException

总而言之,不抛出 a 与其他类型一致 - 当且仅当使用 null 引用时,通过它进行类型。NullReferenceException

请注意,有一种情况是调用 null 可为 null 类型会抛出,它使用 ,因为不是虚拟的,并且当调用值类型时,总是有一个隐含的装箱。其他值类型也是如此,因此:GetType()GetType()

(1).GetType()

被视为:

((object)1).GetType()

但是在可为 null 的类型的情况下,装箱会将带有 false 的类型转换为 null,因此:HasValue

default(int?).GetType()

被视为:

((object)default(int?)).GetType()

这会导致对 null 对象进行调用,从而引发。GetType()

顺便说一句,这让我们明白了为什么不假装是更明智的设计决策——需要这种行为的人总是可以装箱的。如果您希望它通过,请使用,这样语言就无需将其视为强制异常的特例。NullReferenceType((object)myNullableValue).GetString()

编辑

哦,我忘了提到背后的机制.NullReferenceException

测试非常便宜,因为它大多只是忽略问题,然后在发生异常时从操作系统中捕获异常。换句话说,没有测试NullReferenceException

请参阅引发/生成 null 引用异常背后的 CLR 实现是什么?,并注意这些都不适用于可为 null 的值类型。

评论

0赞 supercat 10/31/2012
恕我直言,.net 应该提供一种方法,通过该方法可以显式标记实例方法,以便在 null 实例上调用;虽然这不是可变引用类型的预期行为,但这样的设计将允许不可变引用类型(如具有有意义的默认值的值类型)的行为。String