可空类型的方法

4
有人能解释一下为什么可以在null实例上调用方法吗?
int? num = null;

var s1 = num.ToString();
var s2 = num.HasValue;
var s3 = num.GetHashCode();
var s4 = num.GetValueOrDefault();

var s5 = num.Value; // InvalidOperationException
var s6 = num.GetType(); // NullReferenceException

我可以在调试模式下检查num是否为空,但是如果null实例上调用ToString方法或HasValue getter,为什么可能呢?对于ValueGetType方法,则不可能。所有这些方法或属性都属于Nullable<>类型,对吗?
我自然会期望Value getter返回null值,类似地,HasValue返回false。我还会期望GetType返回Nullable<int>类型信息,或者num is int?num is Nullable<int>有效。为什么不起作用?如何检查num是否为可空类型?
创建一个实例不会改变任何内容:
Nullable<int> num = new Nullable<int>();

幕后的秘密是什么?


根据 Nullable.ToString 的文档:“如果 HasValue 属性为 true,则当前 Nullable<T> 对象的值的文本表示,否则为一个空字符串 ("")。” - MakePeaceGreatAgain
2
Nullable<T>是一个结构体,因此实际上不能为null。这是编译器的特殊处理,使您能够首先执行int? num = null - Zohar Peled
大部分是重复的 https://dev59.com/EGcs5IYBdhLWcg3wlFE2。它已经解释了为什么 ToString 起作用而 GetType 抛出 NullReferenceException 的核心问题。 - Tim Schmelter
2个回答

7
Nullable<T>具有一些编译器魔法,使其看起来像是有一个null值。但实际上并没有。基本上,由于Nullable<T>是一个值类型,它本质上不能为null。它的可空性取决于是否存在值。这意味着您可以调用HasValue(这很重要,因为这是您写num == null时编译器插入的内容)以及其他不依赖于存在值的方法。
至于一些具体的点:
  • ToString是一种实现,类似于在字符串连接中将null值转换为字符串,即导致一个空字符串。你也不想让ToString抛出异常。
  • GetHashCode是必要的,以便将Nullable<T>作为字典中的键或放入哈希集合中。它也不应该抛出异常,因此在没有值时必须返回一些有意义的东西。
  • documentation解释了一些基本概念。

禁止在没有值时访问该值。但正如Zohar PeledMarc Gravell ♦在评论中指出的那样,调用GetType时隐含地发生这种情况:

It would seem logical that the compiler could just silently replace nullableValue.GetType() with typeof(SomeT?), but that would then mean that it always gives confusing answers when compared to

object obj = nullableValue;
Type type = obj.GetType()

You would expect this to work similarly, but obj.GetType() will always return either typeof(T) (not typeof(T?)), or throw a NullReferenceException because T? boxes to either a T or to null (and you can't ask a null what type it is)

编译器特殊处理的结构映射大致如下:
num == null         → !num.HasValue
num != null         → num.HasValue
num = null          → num = new Nullable<int>()
num = 5             → num = new Nullable<int>(5)
(int) num           → num.Value
(object) num        → (object) num.Value         // if HasValue
                    → (object) null              // if !HasValue

有关运算符的额外支持,最重要的是针对非空 T 的比较运算符以及适用于潜在 null 值的各种运算符,如 ??,但这就是要点。


1
为了回答关于为什么GetType会返回null引用异常的问题,请阅读如何:识别可空类型(C#编程指南)。基本上,这是尝试将null装箱的一种方式。 - Zohar Peled
是的,装箱/拆箱的基本行为可以解释这个问题。谢谢,不过我仍然认为要求一个类型并得到空异常并不直观。 - quantumbit

0

其实不是一个答案,只是一条注释。你写到:

我会自然地期望 Value 的 getter 返回 null

不!Nullable<T> 存在的原因就是为了保护你在没有先检查的情况下不获取 null 值。


1
这还不够,Value是一个必须是值类型的T,因此它根本不能null - Joey
1
另外,.Value 的类型为 T,因此根据定义它永远不会返回 null(因为你不能有 Nullable<Nullable<T>>);值得注意的是,.GetValueOrDefault() 永远不会失败地返回。 - Marc Gravell
Joey:我的意思是,那正是有助于解决问题的关键,也是为什么创建了“Nullable<T>”的原因。 - gpvos
我认为它的存在并不是为了保护你,而是允许你在不能容纳空值的类型上拥有空值(从数据库或JSON端点获取),但是是的,我稍微改变了对Value getter的理解。底层值保存着无法为null的原始值,因此通过Value getter读取值不会返回null。这似乎是合乎逻辑的。 - quantumbit

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