CLR via C#, 4th -- 【基本类型】 -- 第19章可空值类型

System.Nullable<T>

[Serializable, StructLayout(LayoutKind.Sequential)]  
public struct Nullable<T> where T : struct {  
 
   // These 2 fields represent the state   
   private Boolean hasValue = false; // Assume null  
   internal T value = default(T);    // Assume all bits zero   
 
   public Nullable(T value) {  
      this.value = value;   
      this.hasValue = true;   
   }  
 
   public Boolean HasValue { get { return hasValue; } }   
 
   public T Value {    
      get {    
         if (!hasValue) {   
            throw new InvalidOperationException(  
               "Nullable object must have a value.");  
         }   
         return value;   
      }   
   }  
 
   public T GetValueOrDefault() { return value; }  
 
   public T GetValueOrDefault(T defaultValue) {   
      if (!HasValue) return defaultValue;  
      return value;  
   }  
  
   public override Boolean Equals(Object other) {  
      if (!HasValue) return (other == null);  
      if (other == null) return false;  
      return value.Equals(other);  
   }  
 
   public override int GetHashCode() {  
      if (!HasValue) return 0;   
      return value.GetHashCode();  
   }  
 
   public override string ToString() {  
      if (!HasValue) return "";  
      return value.ToString();   
   }  
 
   public static implicit operator Nullable<T>(T value) {   
      return new Nullable<T>(value);  
   }  
 
   public static explicit operator T(Nullable<T> value) {   
      return value.Value;   
   }  
}

可以看出,该结构能表示可为null的值类型。由于Nullable<D>本身是值类型,所以它的实例仍然是“轻量级”的。也就是说,实例仍然可以在栈上,而且实例的大小和原始值类型基本一样,只是多了一个Boolean字段。注意Nullable的类型参数T被约束为struct.这是由于引用类型的变量本来就可以为null,所以没必要再去照顾它。

Nullable<Int32> x = 5;   
Nullable<Int32> y = null;   
Console.WriteLine("x: HasValue={0}, Value={1}",  x.HasValue, x.Value);   
Console.WriteLine("y: HasValue={0}, Value={1}",  y.HasValue, y.GetValueOrDefault());

19.1 C#对可空值类型的支持

C#允许用问号表示法来声明并初始化可空值类型变量,

Int32? x = 5;  
Int32? y = null;

Int32?等价于Nullable<Int32>,C#在此基础上更进一步,允许开发人员在可空实例上执行转换和转型。C#还允许向可空实例应用操作符。

private static void ConversionsAndCasting() {   
   // Implicit conversion from non­nullable Int32 to Nullable<Int32>  
   Int32? a = 5;  
 
   // Implicit conversion from 'null' to Nullable<Int32>  
   Int32? b = null;  // Same as "Int32? b = new Int32?();" which sets HasValue to false   
 
   // Explicit conversion from Nullable<Int32> to non ­nullable Int32  
   Int32 c = (Int32) a;  
 
   // Casting between nullable primitive types  
   Double? d = 5; // Int32 ­>Double?  (d is 5.0 as a double)   
   Double? e = b; // Int32?­>Double? (e is null)   
}

可空实例应用操作符

private static void Operators() {  
   Int32? a = 5;  
   Int32? b = null;  
     
   // Unary operators (+  ++  ­  ­­  !  ~)   
   a++;    // a = 6  
   b = ­b; // b = null  
 
   // Binary operators (+  ­  *  /  %  &  |  ^  <<  >>)   
   a = a + 3;  // a = 9  
   b = b * 3;  // b = null;   
 
   // Equality operators (==  !=)  
   if (a == null) { /* no  */ } else { /* yes */ }   
   if (b == null) { /* yes */ } else { /* no  */ }   
   if (a != b)    { /* yes */ } else { /* no  */ }   
 
   // Comparison operators (<>  <=  >=)  
   if (a < b)     { /* no  */ } else { /* yes */ }   
}
  • 一元操作符(+, ++, -, --, ! , ~)
    操作数是null,结果就是null
  • 二元操作符(+, -, *, /, %, &, |, ^, <<, >>)
    两个操作数任何一个是null,结果就是null。但有一个例外,它发生在将&和操作符应用于Boolean?操作数的时候。在这种情况下,两个操作符的行为和SQL的三值逻辑一样。对于这两个操作符,如果两个操作数都不是null,那么操作符和平常一样工作。如果两个操作数都是null,结果就是null.特殊行为仅在其中之一为null时发生。下表列出了针对操作数的true,false和null三个值的各种组合,两个操作符的求值情况。

Operand1 →Operand2

true

false

null

true

& = true 

| = true

& = false

| = true

& = null 

| = true

false

& = false

| = true

& = false

| = false

& = false

| = null

null

& = null 

| = true

& = false

| = null

& = null 

| = null

  • 相等性操作符 (==, !=)
    两个操作数都是null,两者相等。一个操作数是null,两者不相等。两个操作数都不是null,就比较值来判断是否相等。
  • 关系操作符(<, >, <=, >=)
    两个操作数任何一个是null,结果就是false,两个操作数都不是null,就比较值。

编译操作可空实例会生成相当多的IL代码,而且操作可空类型的速度慢于非可空类型。
可定义自己的值类型来重载上述各种操作符。

using System;  
 
internal struct Point {  
   private Int32 m_x, m_y;  
   public Point(Int32 x, Int32 y) { m_x = x; m_y = y; } 
 
   public static Boolean operator==(Point p1, Point p2) {   
      return (p1.m_x == p2.m_x) && (p1.m_y == p2.m_y); 
   } 
 
   public static Boolean operator!=(Point p1, Point p2) {   
      return !(p1 == p2); 
   } 
}
internal static class Program {  
   public static void Main() {   
      Point? p1 = new Point(1, 1); 
      Point? p2 = new Point(2, 2); 
 
      Console.WriteLine("Are points equal? " + (p1 == p2).ToString()); 
      Console.WriteLine("Are points not equal? " + (p1 != p2).ToString()); 
   } 
}

19.2 C#的空接合操作符

C#提供了一个“空接合操作符”(null-coalescing operator),即??操作符,它要获取两个操作数。假如左边的操作数不为null,就返回这个操作数的值。如果左边的操作数为null,就返回右边的操作数的值。利用空接合操作符,可以方便地设置变量的默认值。
空接合操作符的一个好处在于,它既能用于引用类型,也能用于可空值类型。

private static void NullCoalescingOperator() {  
   Int32? b = null;  
 
   // The following line is equivalent to:   
   // x = (b.HasValue) ? b.Value : 123  
   Int32 x = b ?? 123;      
   Console.WriteLine(x);  // "123"  
 
   // The following line is equivalent to:   
   // String temp = GetFilename();  
   // filename = (temp != null) ? temp : "Untitled";   
   String filename = GetFilename() ?? "Untitled";  
}

实际上,??提供了重大的语法上的改进。

第一个改进是??操作符能更好地支持表达式

Func<String> f = () => SomeMethod() ?? "Untitled";
Func<String> f = () => { var temp = SomeMethod();  
return temp != null ? temp : "Untitled";};

第二个改进是??在复合情形中更好用。

String s = SomeMethod1() ?? SomeMethod2() ?? "Untitled";

19.3 CLR对可空值类型的特殊支持

19.3.1 可空值类型的装箱

当CLR对Nullable<T>实例进行装箱时,会检查它是否为null。如果是,CLR不装箱任何东西,直接返回null。如果可空实例不为null,CLR从可空实例中取出值并进行装箱。

// Boxing Nullable<T> is null or boxed T   
Int32? n = null;  
Object o = n;  // o is null   
Console.WriteLine("o is null={0}", o == null);  // "True"   
 
n = 5;  
o = n;   // o refers to a boxed Int32   
Console.WriteLine("o's type={0}", o.GetType()); // "System.Int32"

19.3.2 可空值类型的拆箱

CLR允许将已装箱的值类型T拆箱为一个T或者Nullable<T>。如果对已装箱值类型的引用是null,而且要把它拆箱为一个Nullable<T>,那么CLR会将Nullable<T>的值设为null.

// Create a boxed Int32  
Object o = 5;  
 
// Unbox it into a Nullable<Int32> and into an Int32   
Int32? a = (Int32?) o;  // a = 5   
Int32  b = (Int32)  o;  // b = 5   
 
// Create a reference initialized to null  
o = null;  
 
// "Unbox" it into a Nullable<Int32> and into an Int32  
a = (Int32?) o;       // a = null  
b = (Int32)  o;       // NullReferenceException

19.3.3 通过可空值类型调用GetType

在Nullable<T>对象上调用GetType,CLR实际会“撒谎”说类型是T,而不是Nullable<T>

Int32? x = 5;  
 
// The following line displays "System.Int32"; not "System.Nullable<Int32>"   
Console.WriteLine(x.GetType());

19.3.4 通过可空值类型调接口方法

Int32? n = 5;  
Int32 result = ((IComparable) n).CompareTo(5);  // Compiles & runs OK  
Console.WriteLine(result);                      // 0

假如CLR不提供这一特殊支持,要在可空值类型上调用接口方法,就必须写很繁琐的代码。首先要转型为已拆箱的值类型,然后才能转型为接口以发出调用

Int32 result = ((IComparable) (Int32) n).CompareTo(5);  // Cumbersome
posted @ 2019-12-18 00:28  FH1004322  阅读(92)  评论(0)    收藏  举报