一、簡介 眾所周知,值類型變數不能null,這也是為什麼它們被稱為值類型。但是,在實際的開發過程中,也需要值為null的一些場景。例如以下場景: 場景1:您從資料庫表中檢索可空的整數數據列,資料庫中的null值沒有辦法將此值分配給C#中Int32類型; 場景2:您在UI綁定屬性,但是某些值類型的欄位 ...
目錄
一、簡介
二、語法和用法
三、類型的轉換和運算
四、裝箱與拆箱
五、GetType()方法
六、ToString()方法
七、System.Nullable幫助類
八、語法糖
一、簡介
眾所周知,值類型變數不能null,這也是為什麼它們被稱為值類型。但是,在實際的開發過程中,也需要值為null
的一些場景。例如以下場景:
場景1:您從資料庫表中檢索可空的整數數據列,資料庫中的null
值沒有辦法將此值分配給C#中Int32類型;
場景2:您在UI綁定屬性,但是某些值類型的欄位不是必須錄入的(例如在人員管理中的死亡日期);
場景3:在Java中,java.Util.Date
是一個引用類型,因此可以將此類型的欄位設置為null
。但是,在CLR中,System.DateTime
是一個值類型,DateTime 變數不能null
。如果使用Java編寫的應用程式要將日期/時間傳達給在CLR上運行的Web服務,如果Java應用程式發送是null
, CLR中沒有供對應的類型;
場景4:在函數中傳遞值類型時,如果參數的值無法提供並且不想傳遞,可以使用預設值。但有時預設值並不是最佳的選擇,因為預設值實際也傳遞了一個預設的參數值,邏輯需要特殊的處理;
場景5:當從xml或json反序列化數據時,數據源中缺少某個值類型屬性的值,這種情況很不方便處理。
當然,我們日常工作中還有很多類似的情況。
為了擺脫這些情況,Microsoft在CLR中增加了可為空值類型的概念。為了更清楚理解這一點,我們看一下System.Nullable<T>
類型的邏輯定義:
1 namespace System 2 { 3 [Serializable] 4 public struct Nullable<T> where T : struct 5 { 6 private bool hasValue; 7 internal T value; 8 9 public Nullable(T value) { 10 this.value = value; 11 this.hasValue = true; 12 } 13 14 public bool HasValue { 15 get { 16 return hasValue; 17 } 18 } 19 20 public T Value { 21 get { 22 if (!HasValue) { 23 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue); 24 } 25 return value; 26 } 27 } 28 29 public T GetValueOrDefault() { 30 return value; 31 } 32 33 public T GetValueOrDefault(T defaultValue) { 34 return HasValue ? value : defaultValue; 35 } 36 37 public override bool Equals(object other) { 38 if (!HasValue) return other == null; 39 if (other == null) return false; 40 return value.Equals(other); 41 } 42 43 public override int GetHashCode() { 44 return HasValue ? value.GetHashCode() : 0; 45 } 46 47 public override string ToString() { 48 return HasValue ? value.ToString() : ""; 49 } 50 51 public static implicit operator Nullable<T>(T value) { 52 return new Nullable<T>(value); 53 } 54 55 public static explicit operator T(Nullable<T> value) { 56 return value.Value; 57 } 58 } 59 }查看Nullable的定義
從上面的定義可以總結如下幾點:
- Nullable<T> 類型也是一個值類型;
- Nullable<T> 類型包含一個Value屬性用於表示基礎值,還包括一個
Boolean
類型的HasValue屬性用於表示該值是否為null
; - Nullable<T> 是一個輕量級的值類型。Nullable<T>類型的實例占用記憶體的大小等於一個值類型與一個
Boolean
類型占用記憶體大小之和; - Nullable<T> 的泛型參數T必須是值類型。您只能將Nullable<T>類型與值類型結合使用,您也可以使用用戶定義的值類型。
二、語法和用法
使用Nullable<T>類型,只需指定一個其它值類型的泛型參數T。
示例:
1 Nullable<int> i = 1; 2 Nullable<int> j = null; 3 Nullable<Nullable<int>> k; //這是一個錯誤語法,編譯會報錯。
CLR還提供了一種簡寫的方式。
1 int? i = 1; 2 int? j = null;
可以通過 Value 屬性來獲取基礎類型的值。如下所示,如果不為null
,則將返回實際的值,否則將拋出InvalidOperationException
異常;您可以在調用Value屬性的時,需要檢查是否為null
。
1 Nullable<int> i = 1; 2 Nullable<int> j = null; 3 4 Console.WriteLine(i.HasValue); 5 //輸出結果:True 6 7 Console.WriteLine(i.Value); 8 //輸出結果:1 9 10 Console.WriteLine(j.HasValue); 11 //輸出結果:False 12 13 Console.WriteLine(j.Value); 14 //拋異常: System.InvalidOperationException
三、類型的轉換和運算
C#還支持簡單的語法來使用Nullable<T>類型。它還支持Nullable<T>實例的隱式轉換和轉換。如下示例演示:
1 // 從System.Int32隱式轉換為Nullable<Int32> 2 int? i = 5; 3 4 // 從'null'隱式轉換為Nullable<Int32> 5 int? j = null; 6 7 // 從Nullable<Int32>到Int32的顯式轉換 8 int k = (int)i; 9 10 // 基礎類型之間的轉換 11 Double? x = 5; // 從Int到Nullable<Double> 的隱式轉換 12 Double? y = j; // 從Nullable<Int32> 隱式轉換Nullable<Double>
對Nullable<T> 類型使用操作符,與包含的基礎類型使用方法相同。
- 一元運算符(++、--、 - 等),如果Nullable<T>類型值是
null
時,返回null
; - 二元運算符(+、-、*、/、%、^等)任何操作數是
null
,返回null
; - 對於==運算符,如果兩個操作數都是
null
,則表達式計算結果為true
,如果任何一個操作數是null
,則表達式計算結果為false;如果兩者都不為null
,它照常比較。 - 對於關係運算符(>、<、>=、<=),如果任何一個操作數是
null
,則運算結果是false
,如果操作數都不為null
,則比較該值。
見下麵的例子:
1 int? i = 5; 2 int? j = null; 3 4 // 一元運算符 5 i++; // i = 6 6 j = -j; // j = null 7 8 // 二元運算符 9 i = i + 3; // i = 9 10 j = j * 3; // j = null; 11 12 // 等號運算符(==、!=) 13 var r = i == null; //r = false 14 r = j == null; //r = true 15 r = i != j; //r = true 16 17 // 比較運算符(<、>、<=、>=) 18 r = i > j; //r = false 19 20 i = null; 21 r = i >= j; //r = false,註意,i=null、j=null,但是>=返回的結果是false
Nullable<T>也可以像引用類型一樣,支持三元操作符。
1 // 如果雇員的年齡返回null(出生日期可能未輸入),請設置值0. 2 int age = employee.Age ?? 0; 3 4 // 在聚合函數中使用三元操作符。 5 int?[] numbers = {}; 6 int total = numbers.Sum() ?? 0;
四、裝箱與拆箱
我們已經知道了Nullable<T>是一個值類型,現在我們再來聊一聊它的裝箱與拆箱。
CLR採用一個特殊的規則來處理Nullable<T>類型的裝箱與拆箱。當一個Nullable<T>類型的實例裝箱時,CLR會檢查實例的HasValue屬性:如果是true
,則將實例Value屬性的值進行裝箱後返回結果;如果返回false
,則直接返回null
,不做任何的處理。
在拆箱處理時,與裝箱處反。CLR會檢查拆箱的對象是否為null
,如果是直接創建一個新的實例 new Nullable<T>(),如果不為null
,則將對象拆箱為類型T,然後創建一個新實例 new Nullable<T>(t)。
1 int? n = null; 2 object o = n; //不會進行裝箱操作,直接返回null值 3 4 Console.WriteLine("o is null = {0}", object.ReferenceEquals(o, null)); 5 //輸出結果:o is null = True 6 7 8 n = 5; 9 o = n; //o引用一個已裝箱的Int32 10 11 Console.WriteLine("o's type = {0}", o.GetType()); 12 //輸出結果:o's type = System.Int32 13 14 o = 5; 15 16 //將Int32類型拆箱為Nullable<Int32>類型 17 int? a = (Int32?)o; // a = 5 18 //將Int32類型拆箱為Int32類型 19 int b = (Int32)o; // b = 5 20 21 // 創建一個初始化為null 22 o = null; 23 // 將null變為Nullable<Int32>類型 24 a = (Int32?)o; // a = null 25 b = (Int32)o; // 拋出異常:NullReferenceException
五、GetType()方法
當調用Nullable<T>類型的GetType()
方法時,CLR實際返回類型的是泛型參數的類型。因此,您可能無法區分Nullable<Int32>實例上是一個Int32類型還是Nullable<Int32>。見下麵的例子:
1 int? i = 10; 2 Console.WriteLine(i.GetType()); 3 //輸出結果是:System.Int32 4 5 i = null; 6 Console.WriteLine(i.GetType()); //NullReferenceException
原因分析:
這是因為調用GetType()
方法時,已經將當前實例進行了裝箱,根據上一部分裝箱與拆箱的內容,這裡實際上調用的是Int32類型的GetType()
方法。
調用值類型的GetType()
方法時,均會產生裝箱,關於這一點大家可以自己去驗證。
六、ToString()方法
當調用Nullable<T>類型的ToString()
方法時,如果HasValue屬性的值為false
,則返回String.Empty
,如果該屬性的值為true
,則調用的邏輯是Value.ToString()
。 見下麵的例子:
1 int? i = 10; 2 Console.WriteLine(i.ToString()); 3 //輸出結果:10 4 5 i = null; 6 Console.WriteLine(i.ToString() == string.Empty); 7 //輸出結果:True
七、System.Nullable幫助類
微軟還提供一個同名System.Nullable
的靜態類,包括三個方法:
1 public static class Nullable 2 { 3 //返回指定的可空類型的基礎類型參數。 4 public static Type GetUnderlyingType(Type nullableType); 5 6 //比較兩個相對值 System.Nullable<T> 對象。 7 public static int Compare<T>(T? n1, T? n2) where T : struct 8 9 //指示兩個指定 System.Nullable<T> 對象是否相等。 10 public static bool Equals<T>(T? n1, T? n2) where T : struct 11 }
在這裡面我們重點說明一下GetUnderlyingType(Type nullableType)
方法,另外兩個方法是用來比較值的,大家可以自己研究。
GetUnderlyingType(Type nullableType)
方法是用來返回一個可為空類型的基礎類型,如果 nullableType
參數不是一個封閉的Nullable<T>泛型,則反回null
。
1 Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<int>))); 2 //輸出結果:System.Int32 3 4 Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<>)) == null); 5 //輸出結果:True 6 7 Console.WriteLine(Nullable.GetUnderlyingType(typeof(int)) == null); 8 //輸出結果:True 9 10 Console.WriteLine(Nullable.GetUnderlyingType(typeof(string)) == null); 11 //輸出結果:True
八、語法糖
微軟對Nullable<T>提供了豐富的語法糖來減少開發員的工作量,下麵是我想到供您參考。
簡寫 | 編譯後的語句 |
1 int? i = 5; 2 3 int? j = null; 4 5 var r = i != null; 6 7 var v = (int) i; 8 9 i++; 10 11 i = i + 3; 12 13 r = i != j; 14 15 r = i >= j; 16 17 var k = i + j; 18 19 double? x = 5; 20 21 double? y = j; |
1 int? i = new int?(5); 2 3 int? j = new int?(); 4 5 var r = i.HasValue; 6 7 var v = i.Value; 8 9 i = i.HasValue ? new int?(i.GetValueOrDefault() + 1) : new int?(); 10 11 i = i.HasValue ? new int?(i.GetValueOrDefault() + 3) : new int?(); 12 13 r = i.GetValueOrDefault() != j.GetValueOrDefault() || i.HasValue != j.HasValue; 14 15 r = i.GetValueOrDefault() >= j.GetValueOrDefault() && i.HasValue & j.HasValue; 16 17 int? k = i.HasValue & j.HasValue ? new int?(i.GetValueOrDefault() + j.GetValueOrDefault()) : new int?(); 18 19 double? x = new double?((double) 5); 20 21 double? y = j.HasValue ? new double?((double) j.GetValueOrDefault()) : new double?(); |
參考:
- https://www.codeproject.com/Articles/11854/C-Nullable-Types
- https://www.codeproject.com/Articles/275471/Nullable-Types-in-Csharp-Net
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/nullable-types/using-nullable-types
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/nullable-types/index
轉載請註明出處,原文鏈接:http://www.cnblogs.com/tdfblog/p/Nullable-Types-in-Csharp-Net.html