6.5 泛型结构
与类相似,结构也可以是泛型的。它们非常类似于泛型类,只是没有继承特性。本节介绍泛型结构Nullable<T>,它由.NET Framework定义。
.NET Framework中的一个泛型结构是Nullable<T>。数据库中的数字和编程语言中的数字有显著不同的特征,因为数据库中的数字可以为空,而C#中的数字不能为空。Int32是一个结构,而结构实现同值类型,所以结构不能为空。这种区别常常令人很头痛,映射数据也要多做许多辅助工作。这个问题不仅存在于数据库中,也存在于把XML数据映射到.NET类型。
一种解决方案是把数据库和XML文件中的数字映射为引用类型,因为引用类型可以为空值。但这也会在运行期间带来额外的系统开销。
使用Nullable<T>结构很容易解决这个问题。下面的代码段说明了如何定义Nullable<T>的一个简化版本。结构Nullable<T>定义了一个约束:其中的泛型类型T必须是一个结构。把类定义为泛型类型后,就没有低系统开销这个优点了,而且因为类的对象可以为空,所以对类使用Nullable<T>类型是没有意义的。除了Nullable<T>定义的T类型之外,唯一的系统开销是hasValue布尔字段,它确定是设置对应的值,还是使之为空。除此之外,泛型结构还定义了只读属性HasValue和Value,以及一些运算符重载。把Nullable<T>类型强制转换为T类型的运算符重载是显式定义的,因为当hasValue为false时,它会抛出一个异常。强制转换为Nullable<T>类型的运算符重载定义为隐式的,因为它总是能成功地转换:
public struct Nullable<T> where T: struct { public Nullable(T value) { _hasValue = true; _value = value; } private bool _hasValue; public bool HasValue => _hasValue; private T _value; public T Value { get { if (! _hasValue) { throw new InvalidOperationException("no value"); } return _value; } } public static explicit operator T(Nullable<T> value) => _value.Value; public static implicit operator Nullable<T>(T value) => new Nullable<T>(value); public override string ToString() => ! HasValue ? string.Empty : _value.ToString(); }
在这个例子中,Nullable<T>用Nullable<int>实例化。变量x现在可以用作一个int,进行赋值或使用运算符执行一些计算。这是因为强制转换了Nullable<T>类型的运算符。但是,x还可以为空。Nullable<T>的HasValue和Value属性可以检查是否有一个值,该值是否可以访问:
Nullable<int> x; x = 4; x += 3; if (x.HasValue) { int y = x.Value; } x = null;
因为可空类型使用得非常频繁,所以C#有一种特殊的语法,它用于定义可空类型的变量。定义这类变量时,不使用泛型结构的语法,而使用“? ”运算符。在下面的例子中,变量x1和x2都是可空的int类型的实例:
Nullable<int> x1; int? x2;
可空类型可以与null和数字比较,如上所示。这里,x的值与null比较,如果x不是null,它就与小于0的值比较:
int? x = GetNullableType(); if (x == null) { WriteLine("x is null"); } else if (x < 0) { WriteLine("x is smaller than 0"); }
知道了Nullable<T>是如何定义的之后,下面就使用可空类型。可空类型还可以与算术运算符一起使用。变量x3是变量x1和x2的和。如果这两个可空变量中任何一个的值是null,它们的和就是null。
int? x1 = GetNullableType(); int? x2 = GetNullableType(); int? x3 = x1 + x2;
注意:这里调用的GetNullableType()方法只是一个占位符,它对于任何方法都返回一个可空的int。为了进行测试,简单起见,可以使实现的GetNullableType()返回null或返回任意整数。
非可空类型可以转换为可空类型。从非可空类型转换为可空类型时,在不需要强制类型转换的地方可以进行隐式转换。这种转换总是成功的:
int y1 = 4; int? x1 = y1;
但从可空类型转换为非可空类型可能会失败。如果可空类型的值是null,并且把null值赋予非可空类型,就会抛出InvalidOperationException类型的异常。这就是需要类型强制转换运算符进行显式转换的原因:
int? x1 = GetNullableType(); int y1 = (int)x1;
如果不进行显式类型转换,还可以使用合并运算符从可空类型转换为非可空类型。合并运算符的语法是“? ? ”,为转换定义了一个默认值,以防可空类型的值是null。这里,如果x1是null, y1的值就是0。
int? x1 = GetNullableType(); int y1 = x1 ? ? 0;