可空類型Nullable<T>用法詳解
一、簡介
眾所周知,值類型變量不能null,這也是為什么它們被稱為值類型。但是,在實際的開發(fā)過程中,也需要值為null的一些場景。例如以下場景:
場景1:您從數(shù)據(jù)庫表中檢索可空的整數(shù)數(shù)據(jù)列,數(shù)據(jù)庫中的null值沒有辦法將此值分配給C#中Int32類型;
場景2:您在UI綁定屬性,但是某些值類型的字段不是必須錄入的(例如在人員管理中的死亡日期);
場景3:在Java中,java.Util.Date是一個引用類型,因此可以將此類型的字段設(shè)置為null。但是,在CLR中,System.DateTime是一個值類型,DateTime 變量不能null。如果使用Java編寫的應(yīng)用程序要將日期/時間傳達給在CLR上運行的Web服務(wù),如果Java應(yīng)用程序發(fā)送是null, CLR中沒有供對應(yīng)的類型;
場景4:在函數(shù)中傳遞值類型時,如果參數(shù)的值無法提供并且不想傳遞,可以使用默認(rèn)值。但有時默認(rèn)值并不是最佳的選擇,因為默認(rèn)值實際也傳遞了一個默認(rèn)的參數(shù)值,邏輯需要特殊的處理;
場景5:當(dāng)從xml或json反序列化數(shù)據(jù)時,數(shù)據(jù)源中缺少某個值類型屬性的值,這種情況很不方便處理。
當(dāng)然,我們?nèi)粘9ぷ髦羞€有很多類似的情況。
為了擺脫這些情況,Microsoft在CLR中增加了可為空值類型的概念。為了更清楚理解這一點,我們看一下System.Nullable<T>類型的邏輯定義:
namespace System
{
[Serializable]
public struct Nullable<T> where T : struct
{
private bool hasValue;
internal T value;
public Nullable(T value) {
this.value = value;
this.hasValue = true;
}
public bool HasValue {
get {
return hasValue;
}
}
public T Value {
get {
if (!HasValue) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
}
return value;
}
}
public T GetValueOrDefault() {
return value;
}
public T GetValueOrDefault(T defaultValue) {
return HasValue ? value : defaultValue;
}
public override bool Equals(object other) {
if (!HasValue) return other == null;
if (other == null) return false;
return value.Equals(other);
}
public override int GetHashCode() {
return HasValue ? value.GetHashCode() : 0;
}
public override string ToString() {
return HasValue ? 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;
}
}
}從上面的定義可以總結(jié)如下幾點:
- Nullable<T> 類型也是一個值類型;
- Nullable<T> 類型包含一個Value屬性用于表示基礎(chǔ)值,還包括一個
Boolean類型的HasValue屬性用于表示該值是否為null; - Nullable<T> 是一個輕量級的值類型。Nullable<T>類型的實例占用內(nèi)存的大小等于一個值類型與一個
Boolean類型占用內(nèi)存大小之和; - Nullable<T> 的泛型參數(shù)T必須是值類型。您只能將Nullable<T>類型與值類型結(jié)合使用,您也可以使用用戶定義的值類型。
二、語法和用法
使用Nullable<T>類型,只需指定一個其它值類型的泛型參數(shù)T。
示例:
Nullable<int> i = 1;
Nullable<int> j = null;
Nullable<Nullable<int>> k; //這是一個錯誤語法,編譯會報錯。CLR還提供了一種簡寫的方式。
int? i = 1;
int? j = null;可以通過 Value 屬性來獲取基礎(chǔ)類型的值。如下所示,如果不為null,則將返回實際的值,否則將拋出InvalidOperationException異常;您可以在調(diào)用Value屬性的時,需要檢查是否為null。
Nullable<int> i = 1;
Nullable<int> j = null;
Console.WriteLine(i.HasValue);
//輸出結(jié)果:True
Console.WriteLine(i.Value);
//輸出結(jié)果:1
Console.WriteLine(j.HasValue);
//輸出結(jié)果:False
Console.WriteLine(j.Value);
//拋異常: System.InvalidOperationException三、類型的轉(zhuǎn)換和運算
C#還支持簡單的語法來使用Nullable<T>類型。它還支持Nullable<T>實例的隱式轉(zhuǎn)換和轉(zhuǎn)換。如下示例演示:
// 從System.Int32隱式轉(zhuǎn)換為Nullable<Int32>
int? i = 5;
// 從'null'隱式轉(zhuǎn)換為Nullable<Int32>
int? j = null;
// 從Nullable<Int32>到Int32的顯式轉(zhuǎn)換
int k = (int)i;
// 基礎(chǔ)類型之間的轉(zhuǎn)換
Double? x = 5; // 從Int到Nullable<Double> 的隱式轉(zhuǎn)換
Double? y = j; // 從Nullable<Int32> 隱式轉(zhuǎn)換Nullable<Double>對Nullable<T> 類型使用操作符,與包含的基礎(chǔ)類型使用方法相同。
- 一元運算符(++、--、 - 等),如果Nullable<T>類型值是
null時,返回null; - 二元運算符(+、-、*、/、%、^等)任何操作數(shù)是
null,返回null; - 對于==運算符,如果兩個操作數(shù)都是
null,則表達式計算結(jié)果為true,如果任何一個操作數(shù)是null,則表達式計算結(jié)果為false;如果兩者都不為null,它照常比較。 - 對于關(guān)系運算符(>、<、>=、<=),如果任何一個操作數(shù)是
null,則運算結(jié)果是false,如果操作數(shù)都不為null,則比較該值。
見下面的例子:
int? i = 5;
int? j = null;
// 一元運算符
i++; // i = 6
j = -j; // j = null
// 二元運算符
i = i + 3; // i = 9
j = j * 3; // j = null;
// 等號運算符(==、!=)
var r = i == null; //r = false
r = j == null; //r = true
r = i != j; //r = true
// 比較運算符(<、>、<=、>=)
r = i > j; //r = false
i = null;
r = i >= j; //r = false,注意,i=null、j=null,但是>=返回的結(jié)果是falseNullable<T>也可以像引用類型一樣,支持三元操作符。
// 如果雇員的年齡返回null(出生日期可能未輸入),請設(shè)置值0.
int age = employee.Age ?? 0;
// 在聚合函數(shù)中使用三元操作符。
int?[] numbers = {};
int total = numbers.Sum() ?? 0;四、裝箱與拆箱
我們已經(jīng)知道了Nullable<T>是一個值類型,現(xiàn)在我們再來聊一聊它的裝箱與拆箱。
CLR采用一個特殊的規(guī)則來處理Nullable<T>類型的裝箱與拆箱。當(dāng)一個Nullable<T>類型的實例裝箱時,CLR會檢查實例的HasValue屬性:如果是true,則將實例Value屬性的值進行裝箱后返回結(jié)果;如果返回false,則直接返回null,不做任何的處理。
在拆箱處理時,與裝箱處反。CLR會檢查拆箱的對象是否為null,如果是直接創(chuàng)建一個新的實例 new Nullable<T>(),如果不為null,則將對象拆箱為類型T,然后創(chuàng)建一個新實例 new Nullable<T>(t)。
int? n = null;
object o = n; //不會進行裝箱操作,直接返回null值
Console.WriteLine("o is null = {0}", object.ReferenceEquals(o, null));
//輸出結(jié)果:o is null = True
n = 5;
o = n; //o引用一個已裝箱的Int32
Console.WriteLine("o's type = {0}", o.GetType());
//輸出結(jié)果:o's type = System.Int32
o = 5;
//將Int32類型拆箱為Nullable<Int32>類型
int? a = (Int32?)o; // a = 5
//將Int32類型拆箱為Int32類型
int b = (Int32)o; // b = 5
// 創(chuàng)建一個初始化為null
o = null;
// 將null變?yōu)镹ullable<Int32>類型
a = (Int32?)o; // a = null
b = (Int32)o; // 拋出異常:NullReferenceException五、GetType()方法
當(dāng)調(diào)用Nullable<T>類型的GetType()方法時,CLR實際返回類型的是泛型參數(shù)的類型。因此,您可能無法區(qū)分Nullable<Int32>實例上是一個Int32類型還是Nullable<Int32>。見下面的例子:
int? i = 10;
Console.WriteLine(i.GetType());
//輸出結(jié)果是:System.Int32
i = null;
Console.WriteLine(i.GetType()); //NullReferenceException原因分析:
這是因為調(diào)用GetType()方法時,已經(jīng)將當(dāng)前實例進行了裝箱,根據(jù)上一部分裝箱與拆箱的內(nèi)容,這里實際上調(diào)用的是Int32類型的GetType()方法。
調(diào)用值類型的GetType()方法時,均會產(chǎn)生裝箱,關(guān)于這一點大家可以自己去驗證。
六、ToString()方法
當(dāng)調(diào)用Nullable<T>類型的ToString()方法時,如果HasValue屬性的值為false,則返回String.Empty,如果該屬性的值為true,則調(diào)用的邏輯是Value.ToString()。 見下面的例子:
int? i = 10;
Console.WriteLine(i.ToString());
//輸出結(jié)果:10
i = null;
Console.WriteLine(i.ToString() == string.Empty);
//輸出結(jié)果:True七、System.Nullable幫助類
微軟還提供一個同名System.Nullable的靜態(tài)類,包括三個方法:
public static class Nullable
{
//返回指定的可空類型的基礎(chǔ)類型參數(shù)。
public static Type GetUnderlyingType(Type nullableType);
//比較兩個相對值 System.Nullable<T> 對象。
public static int Compare<T>(T? n1, T? n2) where T : struct
//指示兩個指定 System.Nullable<T> 對象是否相等。
public static bool Equals<T>(T? n1, T? n2) where T : struct
}在這里面我們重點說明一下GetUnderlyingType(Type nullableType)方法,另外兩個方法是用來比較值的,大家可以自己研究。
GetUnderlyingType(Type nullableType)方法是用來返回一個可為空類型的基礎(chǔ)類型,如果 nullableType 參數(shù)不是一個封閉的Nullable<T>泛型,則反回null。
Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<int>)));
//輸出結(jié)果:System.Int32
Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<>)) == null);
//輸出結(jié)果:True
Console.WriteLine(Nullable.GetUnderlyingType(typeof(int)) == null);
//輸出結(jié)果:True
Console.WriteLine(Nullable.GetUnderlyingType(typeof(string)) == null);
//輸出結(jié)果:True八、語法糖
微軟對Nullable<T>提供了豐富的語法糖來減少開發(fā)員的工作量,下面是我想到供您參考。
簡寫
int? i = 5;
int? j = null;
var r = i != null;
var v = (int) i;
i++;
i = i + 3;
r = i != j;
r = i >= j;
var k = i + j;
double? x = 5;
double? y = j;編譯后的語句
int? i = new int?(5);
int? j = new int?();
var r = i.HasValue;
var v = i.Value;
i = i.HasValue ? new int?(i.GetValueOrDefault() + 1) : new int?();
i = i.HasValue ? new int?(i.GetValueOrDefault() + 3) : new int?();
r = i.GetValueOrDefault() != j.GetValueOrDefault() || i.HasValue != j.HasValue;
r = i.GetValueOrDefault() >= j.GetValueOrDefault() && i.HasValue & j.HasValue;
int? k = i.HasValue & j.HasValue ? new int?(i.GetValueOrDefault() + j.GetValueOrDefault()) : new int?();
double? x = new double?((double) 5);
double? y = j.HasValue ? new double?((double) j.GetValueOrDefault()) : new double?();到此這篇關(guān)于可空類型Nullable<T>用法詳解的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.net中前臺javascript與后臺c#函數(shù)相互調(diào)用問題
.net中前臺javascript與后臺c#函數(shù)相互調(diào)用問題...2007-12-12
c# 獲取CookieContainer的所有cookies函數(shù)代碼
這篇文章主要介紹了c# 獲取CookieContainer所有cookies的函數(shù)代碼,需要的朋友可以參考下2013-06-06
WPF中鼠標(biāo)/鍵盤/拖拽事件以及用行為封裝事件詳解
這篇文章主要為大家詳細(xì)介紹了WPF中常用的鼠標(biāo)事件、鍵盤事件以及注意事項,同時使用一個案例講解了拓展事件,感興趣的小伙伴可以了解一下2023-03-03
C#如何優(yōu)雅的對WinForm窗體應(yīng)用程序進行權(quán)限控制
經(jīng)常會出現(xiàn)winfrom頁面需要加載權(quán)限樹,下面這篇文章主要給大家介紹了關(guān)于C#如何優(yōu)雅的對WinForm窗體應(yīng)用程序進行權(quán)限控制的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11

