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 nonnullable 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
浙公网安备 33010602011771号