购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

4.7 可空值类型

引用类型可以使用空引用表示一个不存在的值,然而值类型不能直接表示为null:

若要在值类型中表示null,则必须使用特殊的结构即可空值类型。可空值类型是由值类型后加一个“?”表示的:

4.7.1 Nullable<T>结构体

T? 会转换为 System.Nullable<T> ,它是一个轻量级的不可变的结构体。它只有两个字段,分别代表 Value HasValue System.Nullable<T> 的本质是很简单的:

以下代码:

将转换为:

HasValue 为false时,试图获得 Value 会抛出 InvalidOperationException 异常。当 HasValue 为true时, GetValueOrDefault() 会返回 Value ,否则返回 new T() 或者一个特定的自定义默认值。

T? 的默认值为 null

4.7.2 隐式和显式的可空值类型转换

T T? 的转换是隐式的,反之则是显式的。例如:

显式强制转换与直接调用可空对象的 Value 属性实质上是等价的。因此,当 HasValue 为false的时候将抛出 InvalidOperationException

4.7.3 装箱拆箱可空值

T? 类型的对象装箱后,堆中的装箱值包含的是 T ,而非 T? 。这种优化方式是可行的,因为装箱值已经是一个可以赋值为null的引用类型了。

C#允许通过 as 运算符对一个可空值类型进行拆箱。如果强制转换出错,那么结果为 null

4.7.4 运算符优先级提升

Nullable<T> 结构并没有定义诸如 < > == 之类的运算符。尽管如此,以下代码仍然能够正常编译和执行:

这是因为编译器会从实际值类型借用或者“提升”小于运算符。在语义上,它会将前面的比较表达式转换为如下语句:

换句话说,如果 x y 都有值,那么它会通过 int 的小于运算符做比较。否则它会返回 false

运算符提升意味着可以隐式使用 T 的运算符来处理 T? 。可以专门针对 T? 进行运算符重载来实现特殊的空值行为。但是在大多数情况下,最好通过编译器来自动地应用系统的空值逻辑。以下是一些示例:

编译器会根据运算符的分类来执行空值逻辑。下面将介绍这些不同的规则。

4.7.4.1 相等运算符(==和!=)

提升后的相等运算符可以像引用类型那样处理空值,这意味着两个 null 值是相等的。

而且:

· 如果只有一个操作数为null,那么两个操作数不相等。

· 如果两个操作数都不能为null,则比较它们的 Value

4.7.4.2 关系运算符(<、<=、>=、>)

对于关系运算符而言比较null操作数是没有意义的。因此比较空值和另外一个空值或非空值的结果都是 false

4.7.4.3 其他运算符(+、-、*、/、%、&、|、^、<<、>>、+、++、--、!和~)

当任意一个操作数为 null 时,此类运算符都会返回 null 。SQL用户是非常熟悉这种模式的:

唯一的例外是计算 bool? & | 运算符,我们稍后会进行详细讨论。

4.7.4.4 混合使用可空和非空类型的操作数

混合使用可空或不可空值类型是可行的,这是因为 T T? 之间存在着隐式转换机制:

4.7.5 在bool?上使用&和|运算符

如果操作数的类型为 bool? ,那么 & | 运算符会将 null 作为一个未知值(unknown value)看待。所以 null | true 应当返回 true ,因为:

· 如果未知值是假的,那么结果为真。

· 如果未知值是真的,那么结果为真。

类似地, null & false 的结果为 false 。这个行为和SQL非常相似,以下例子说明了一些其他组合用法:

4.7.6 可空值类型和null运算符

可空值类型与 ?? 运算符相辅相成(请参见2.10.1节),如以下示例所示:

在可空值类型上使用 ?? 运算符相当于调用 GetValueOrDefault 方法并提供一个显式的默认值,但变量如果不是 null 的话则不会使用默认值。

可空值类型同样适用于 null 条件运算符(请参见2.10.3节)。在下面的例子中,length的值为 null

结合使用null合并运算符和null条件运算符可最终得到0而不是null:

4.7.7 可空值类型的应用场景

可空值类型常用来表示未知的值,尤其是在数据库编程中最为常见。数据编程通常需要将类映射到具有可空列的数据表中。如果这些列是字符串类型(例如,Customer表的EmailAddress列),这样就没有任何问题,因为字符串是一种CLR的引用类型,所以可以为null。然而有些SQL列的类型是值类型,因此使用可空值类型可以将这些SQL的列映射到CLR中。例如:

可空值类型还可以表示支持字段,即所谓的环境属性(ambient property)。如果环境属性的值为null,则返回父一级的值。例如:

4.7.8 可空值类型的替代方案

在可空值类型成为C#语言的一部分之前(例如C# 2.0以前的版本),也有许多处理可空值类型的方式。出于历史原因,这些方式现在仍然存在于.NET库中,其中一种方式是将一个特定的非空值指定为“空值”。字符串和数组类中就使用了这种方式。例如, String.IndexOf 在找不到字符时会返回一个特殊的“魔法值” -1

然而, Array.IndexOf 只有在索引是基于 0 的时候才会返回 -1 。实际的规则是 IndexOf 返回比数据下限小1的值。在下一个例子中, IndexOf 在没有找到某个元素的时候返回 0

指定“魔法值”会造成各种问题,以下列举了一些原因:

· 每一个值类型有不同的空值表示方式。而与之相反,使用可空值类型可以用一种通用模式处理任意的值类型。

· 可能无法找到一个合理的值。例如,在上述的例子中,我们无法总是使用 -1 。更早的例子中表示一个未知账号的余额的方式也有相同的问题。

· 如果忘记对魔法值进行测试可能导致长期忽略不正确的数据,直至在后续运行中出现一个出乎意料的结果。而如果忘记测试 HasValue 为null的情况,则会马上抛出 InvalidOperationException

· 没有在类型层面上处理null值的能力。类型可以传达程序的意图,并允许编译器检查其正确性,从而和编译器的规则保持一致。 LkUIBtZ4BuEmk6M0JjZ0DIijtI6bACrNWC3oo0TQGPULreHlaJRJDkQeQRr4bJGC

点击中间区域
呼出菜单
上一章
目录
下一章
×