为什么空语句 ToString() 返回一个空字符串?

17

我想知道下面两个语句之间的区别:

  1. Causes a NullReferenceException - it is OK.

    object test1 = default(int?);
    object result = test1.ToString();
    
  2. Returns an empty string "", why?

    object test2 = default(int?).ToString();
    
  3. This is same as 2.

    int? test3 = null;
    if (test3.ToString() == string.Empty) //returns true
    {
        Console.WriteLine("int? = String.Empty, isn't it strange?").
    }
    
  4. And just for fun - I can prove that bool can be equal to int value (hmmm how? bool can be only false, or true and int never can be that).

    if (default(int?).ToString() == default(bool?).ToString()) //returns true because both are empty strings
    {
        Console.WriteLine("int = bool");
    }
    

注意: default(int?) 返回 null。


Nullable<T> 是一个值类型,实际上不能为 null。它只是以一种编程方式编写,使得与 null 的比较在 HasValue 属性为 false 时返回 true。(将其设置为 null 将会将 HasValue 属性设置为 false - dialer
1
https://dev59.com/f2gu5IYBdhLWcg3wRlFh?rq=1 - usha
1
“object test2 = default(int?).ToString();” 在 ILSpy 中被翻译为 “object test2 = ((object)null).ToString();”,但第二行会抛出异常。可能是 ILSpy 的问题吗?否则 Servy 的答案看起来是完美的。 - Habib
1
为了更有趣,尝试在未抛出异常的情况下调用ToString()的默认(int?)上调用GetType()。装箱可能有点棘手。 - Dan Bryant
2个回答

32
int?是一个Nullable<int>Nullable是一个结构体,这意味着它是一个值类型,而不是引用类型。实际上,test3并没有设置为null。将null赋值给它实际上会创建一个新的结构,该结构的HasValue字段被设置为false而不是true。(需要特殊编译器支持才能进行这样的隐式转换;因此您无法拥有自己的执行此操作的MyNullable<T>类型。)
由于可空对象具有实际值,因此可以调用ToString并提供有意义的值,在这种情况下是空字符串。
当通过将可空值放入object变量中来装箱时,它将导致在该变量中存储一个实际的null值,这就是为什么调用其上的方法会导致NRE的原因。
当可空值类型被装箱时,它不会将可空值类型装箱;相反,如果没有值,则将null分配给对象,如果有值,则该基础值被解包然后装箱。(运行时需要特殊支持才能实现此行为,这是您无法制作自己的MyNullable<T>类型的另一个原因。)

1
在C语言中,使用标记.访问左操作数的成员,使用->访问左操作数所持有指针(引用)指向的对象的成员。如果->的左操作数为空,则会出现空引用。在C#中,当应用于值类型时,.具有前一种含义,而当应用于类类型时,.具有后一种含义;在C#中对空的类类型变量使用.将触发NullReferenceException异常。
.NET类型Nullable<T>是一个两个字段的结构,其中包含一个布尔字段,指示它是否具有有意义(非空)的值,并且值类型T的字段表示该值是什么(如果不为null)。尽管编译器允许将Nullable<T>分配为null或将其与null进行比较,但在前一种情况下的null实际上只是默认实例的简写形式(其中“has-value”标志为false,值字段保存类型T的默认值),而与null的比较实际上只是检查HasValue属性的简写形式(它反过来查看Boolean字段)。在例如为“null”的Nullable<int>上调用ToString在语义上与在包含False0值的BooleanInt32的任何其他结构上调用它没有区别。编译器处理可空类型的方式有几种,但默认值的Nullable<int>保持[False, 0],而不是null引用。
请注意,第一个示例由于框架应用于可空类型的“特殊”规则之一而抛出异常。将值类型转换为Object或另一个引用类型通常会指示运行时创建一个对象,该对象将表现为具有与结构相匹配的字段和成员的类对象,并返回对该新对象的引用——这个过程称为“装箱”。因为可空类型的设计者希望允许代码使用if (NullableThing == null)而不是(在我看来语义更清晰的)if (!NullableThing.HasValue),所以运行时被实现为装箱一个其HasValue字段为false的可空类型将产生null引用,而不是对Nullable<T>的默认值实例的引用。虽然我认为有些值类型使用不同的装箱和拆箱方法是有意义的情况,但可空类型是唯一可以使用除正常装箱外其他方法的类型。

2
这个解释是正确的,但不完整:它没有解释为什么在第一种情况下会抛出异常...这是因为“null”可空类型被装箱成了一个空引用。 - Thomas Levesque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接