可空值类型令值类型可以使用空值,而可空引用类型(C# 8+)却正相反。启用该功能可以一定程度上防止引用类型的值为空值,并进一步避免 NullReferenceException 。
可空引用类型提供的这种安全性完全是由编译器来实现的。当编译器发现代码可能产生 NullReferenceException 时,就会生成一系列警告。
要启用可空引用类型功能,需要在 .csproj 工程文件中添加相应的 Nullable 元素(这样做将在整个工程范围内启用该功能):
也可以在代码中直接使用以下指令来圈定该功能的生效范围:
功能启用之后,编译器将默认认定引用非空。如需令引用类型接受空值且不生成编译器警告,则需要添加 ? 后缀来标记该引用为可空引用。在以下示例中, s1 是不可空的,而 s2 是可空的:
由于可空引用类型是编译期功能,因此 string 和 string? 在运行时的行为是一致的。相反,可空值类型则在类型系统引入了名为 Nullable<T> 的新类型。
由于 x 没有初始化,因此以下代码也会产生警告:
将 x 初始化即可消除警告。在字段上初始化或在构造器中初始化均可。
当对可空引用类型执行解引用操作时,若编译器认为相应操作可能产生 NullReferenceException ,则仍然会生成警告。在以下示例中,访问字符串对象的 Length 属性会生成警告:
此时,可以使用null包容运算符(!)来消除这个警告:
上述示例中的null包容运算符是危险的,因为这有可能抛出 NullReferenceException ,而这恰恰是我们希望避免的。因此可以做如下修正:
注意,此处无须使用null包容运算符。这是因为编译器通过静态流程分析——至少在这种简单的例子中——足以推断出解引用操作是安全的,不会产生 NullReferenceException 。
编译器提供的检查和警告手段并非无懈可击,这种检查能够覆盖的情况是非常有限的。例如,编译器无法得知数组的元素是否已经初始化,因此以下程序不会产生编译器警告:
使用 #nullable enable 指令(或者在工程文件中添加 <Nullable>enable</Nullable> 配置)启用可空引用类型支持将产生如下两个效果:
· 它启用了可空注解上下文(nullable annotation context)。该上下文指示编译器将所有引用类型的变量声明均视为非空类型,除非该变量有 ? 后缀的修饰。
· 它启用了可空警告上下文(nullable warning context)。该上下文指示编译器在发现代码可能产生 NullRefernceException 的时候生成一个警告。
有时,我们希望区分这两个概念,仅仅启用注解上下文或仅仅启用警告上下文(尽管只启用警告上下文的意义并不大):
(上述技巧同样适用于 #nullable disable 和 #nullable restore 。)
也可以在工程文件中应用相应的配置:
在特定的类或程序集上仅仅启用注解上下文可以作为将可空引用类型引入遗留代码库的良好开端。正确的注解public成员可以让遗留代码中的类或程序集和其他的类和程序集进行良好的配合,这样它们不但可以享受可空引用类型带来的好处,同时无须处理类或程序集中产生的警告。
对于全新的工程,从外部整体启用可空上下文是明智之举。除此之外,还可以再进一步,即将和空值相关的警告视为错误。这样除非解决所有和空值相关的警告,否则工程就无法完成编译: