C#高级编程(第10版) C# 6 & .NET Core 1.0 (.NET开发经典名著)
上QQ阅读APP看书,第一时间看更新

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;