I call it my billion-dollar mistake. It was the invention of the null reference in 1965.
–Tony Hoare, 2009
这是 NULL 的发明者 Tony Hoare 在 2009 年的一段演讲内容。许多使用 null 引用的语言中,写的最多重复的大概就是if(x!=null)
了
本文只涉及我熟悉的 C#语言,大概扯一下 NULL 的各种坑
NULL
Hoare 说引入 NULL 是因为实现更简单,而他的工作是确保一切引用都是安全的。也就是说,重点不是如何表示空值,而是避免使用空引用。但是显然if(x!=null)
并不是最佳实现。首先容易忘 而且,由于 CPU 的分支预测,假如一个值是否为 NULL 是完全随机的,if 判空会使性能下降。
Null 的存在
在 C#8.0 之前,引用类型都是可为 null 的,而值类型可以通过?
运算符使其成为Nullable Type
可空类型(Nullable
类型仍然是值类型),通过给值类型加?
是Nullable<T>
的语法糖,它有一个HasValue
属性和Value
属性,如果HasValue
是false
,尝试访问Value
时会引发InvalidOperationException
异常,而不是NullReferenceException
,至于为什么后者那么不受待见…如果你还不知道,说明遇到的 null 还不够多!
也就是说,遇到Nullable<T>
类型,要么判断HasValue
,要么触发InvalidOperationException
。Rust 的Option<T>
枚举也很类似,但是由于它是枚举类型,你必须判断是否有值,这样完全不会引起 panic。
可为 null 的引用类型
这是 C#8.0 的新特性。说实话这个名字很怪,因为引用类型一直是 Nullable 的。既然单独提出 Nullable,那么其实是默认所有引用类型,如果不加修饰,也是不可为 null 的。但是如果直接给引用类型加?
,编译器会给出警告:
1 | warning CS8632: 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 |
说来惭愧,到了.NET5,我也没用过Nullable Reference Type
(所以才有这篇文章)
要消除警告,需要使用#nullable enable
和#nullable restore
(尽量使用restore
而不是 disable
,因为项目默认设置可能会改变,这时候如果使用disable
…出大问题);或者,给项目属性加一个
1 | <Nullable>enable</Nullable> |
VS2022+.NET6的一些新项目模板默认启用Nullable。这时应当为所有引用类型提供初始化值。这对POCO类很重要,因为许多数据库也是不推荐使用NULL的。
启用Nullable后,不为引用类型指定初始值会触发警告:
1 | public class MyClass |
1 | warning CS8618: 在退出构造函数时,不可为 null 的 字段“Name”必须包含非 null 值。请考虑将 字段 声明为可以为 |
你可以将它声明为Nullable,或者,对于常见的集合(字符串姑且也算),直接为它指定一个空集合。
为什么应该使用Nullable
判断Nullable<T>.HasValue
和 if(x != null)
其实没啥差别,除了前者异常处理更友好。
更好的做法是为引用类型指定初始值。如果说C#8.0之前的Nullable是为了给值类型创造null,现在的Nullable则是为了毁灭null。
在乎初始值占的那点内存?真的在乎那一点内存,还写什么C#!
Permalink: http://blog.artiga.top/2021/null-the-mistake/
本文采用CC BY-NC-SA 4.0许可
Comments