在程序開發中,有時候需要值類型也為可空類型,比如,在數據庫中,我們可以把一個日期Datetime設置為null。
在C# 2.0中就出現了可空類型,允許值類型也可以為空(null),可空類型的實現基於C#泛型。
可空類型的核心是System.Nullable<T>,同時靜態類System.Nullable為可空類型提供了很多實用的方法。下面分別看看可空類型的這兩個重要組成部分。
通過ILSpy我們可以查看這個類型的C#代碼:
從上面的圖中可以看到關於System.Nullable<T>的一些關鍵點:
對於任何具體的可空類型來說,T的類型為可空類型的基礎類型(underlying type),例如Nullable<int>的基礎類型就是int。
通過上面代碼還可以看到,Nullable<T>有兩個重要的屬性,HasValue和Value。通過它們可以了解可空類型是怎麼工作的:
下面看一個可空類型的簡單例子,進一步了解一下可空類型:
static void Display(Nullable<int> x) { Console.WriteLine("HasValue: {0}", x.HasValue); if (x.HasValue) { Console.WriteLine("Value: {0}", x.Value); Console.WriteLine("Explicit conversion: {0}", (int)x); } Console.WriteLine("GetValueOrDefault(): {0}", x.GetValueOrDefault()); Console.WriteLine("GetValueOrDefault(10): {0}", x.GetValueOrDefault(10)); Console.WriteLine("ToString(): {0}", x.ToString()); Console.WriteLine("GetHashCode(): {0}", x.GetHashCode()); Console.WriteLine(); } static void Main(string[] args) { Nullable<int> x = 5; Display(x); x = new Nullable<int>(9); Display(x); x = new Nullable<int>(); Display(x); Console.Read(); }
程序的輸出為:
通過這段代碼可以看到HasValue和Value的使用,以及Nullable<T>中一些常用的方法。
注意,在這段代碼中,下面兩句的IL代碼是一樣的:
C#代碼
Nullable<int> x = 5; x = new Nullable<int>(9);
IL代碼
IL_0004: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0) IL_0015: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
這裡涉及了包裝(wrapping)和拆包(unwrapping)的概念:將T的一個實例轉換成Nullable<T>的一個實例的過程在C#中成為包裝,相反的過程成為拆包。這個概念跟裝箱和拆箱不一樣,後面會看到Nullable<T>的裝箱和拆箱。
從前面的分析可以看到Nullable<T>是一個結構,也就是一個值類型。也就是說,當我們把可空類型轉換成一個引用類型的時候需要進行裝箱操作。
對於Nullable<T>的裝箱和拆箱可以概括為:
看一個關於可空類型裝箱和拆箱的例子:
static void Main(string[] args) { Nullable<int> x = 5; //有值的可空類型裝箱 object boxed = x; Console.WriteLine(x.GetType()); //拆箱為普通類型 int normal = (int)boxed; Console.WriteLine(normal); //拆箱為可空類型 x = (Nullable<int>)boxed; Console.WriteLine(x); x = new Nullable<int>(); //空的可空類型裝箱 boxed = x; Console.WriteLine(boxed == null); //拆箱為可空類型 x = (Nullable<int>)boxed; Console.WriteLine(x.HasValue); }
輸出:
System.Nullable是一個靜態類,只包含三個靜態方法,大家可以通過ILSpy進行查看,這裡就不上圖了。
下面兩個方法是比較方法:
public static int Compare<T>(T? n1, T? n2) where T : struct public static bool Equals<T>(T? n1, T? n2) where T : struct
下面這個方法用來獲得可空類型的基礎類型:
public static Type GetUnderlyingType(Type nullableType)
在C# 2.0中,我們可以使用?修飾符來表示可空類型。
下面的C#語句具有相同的IL代碼。
Nullable<int> x = 5; int? y = 5;
IL_0004: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0) IL_000d: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
C#編譯器允許使用null在比較和賦值中表示一個可空類型的空值。
對於下面的代碼,通過IL可以發現"x == null"實際調用的是HasValue屬性進行比較。
int? x = null; Console.WriteLine(x == null);
IL_000b: call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
C# 2.0中出現的可空類型解決了我們很多的問題,可空類型的相關知識還是比較容易理解的。
在使用中,我們可以直接使用?修飾符來創建可空值類型。