標籤:
在程式開發中,有時候需要實值型別也為可空類型,比如,在資料庫中,我們可以把一個日期Datetime設定為null。
在C# 2.0中就出現了可空類型,允許實值型別也可以為空白(null),可空類型的實現基於C#泛型。
可空類型基本知識
可空類型的核心是System.Nullable<T>,同時靜態類System.Nullable為可空類型提供了很多實用的方法。下面分別看看可空類型的這兩個重要組成部分。
System.Nullable<T>
通過ILSpy我們可以查看這個類型的C#代碼:
從上面的圖中可以看到關於System.Nullable<T>的一些關鍵點:
- Nullable<T>是一個泛型型別
- 型別參數T有一個實值型別的約束(根據實值型別約束T : struct,T不能為可空類型,也就是說Nullable<Nullable<int>>是不允許的)
- Nullable<T>是一個實值型別(是一個struct)
對於任何具體的可空類型來說,T的類型為可空類型的基礎類型(underlying type),例如Nullable<int>的基礎類型就是int。
通過上面代碼還可以看到,Nullable<T>有兩個重要的屬性,HasValue和Value。通過它們可以瞭解可空類型是怎麼工作的:
- 如果一個可空實值型別存在一個真正的值,那麼Value就代表這個值本身,同時HasValue值為true
- 如果一個可空實值型別為空白,那麼HasValue為false,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>是一個結構,也就是一個實值型別。也就是說,當我們把可空類型轉換成一個參考型別的時候需要進行裝箱操作。
對於Nullable<T>的裝箱和拆箱可以概括為:
- Nullable<T>的執行個體要麼裝箱為空白引用,要麼裝箱成T的一個以裝箱的值
- 已裝箱的值可以拆箱成普通類型,或者拆箱為對於的可空類型
- 拆箱一個Null 參考時,如果拆箱為普通類型,會拋出一個NullReferenceException的異常
- 如果拆箱成恰當的可控類型,就會拆箱成一個沒有值的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
System.Nullable是一個靜態類,只包含三個靜態方法,大家可以通過ILSpy進行查看,這裡就不了。
下面兩個方法是比較方法:
public static int Compare<T>(T? n1, T? n2) where T : structpublic 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)
使用null進行賦值和比較
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中出現的可空類型解決了我們很多的問題,可空類型的相關知識還是比較容易理解的。
在使用中,我們可以直接使用?修飾符來建立可空實值型別。
C#可空類型