C#基礎(chǔ)語法:可空類型詳解
以下是System.Nullable<T>在FCL中的定義。
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Nullable<T> where T :struct
{
private Boolean hasValue= false;
internal T value= default(T);
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);
}
}
可以看出 null 的類型的每個實例都具有兩個公共的只讀屬性:
1.HasValue
HasValue 屬于 bool 類型。當(dāng)變量包含非 null 值時,它被設(shè)置為 true。
2.Value
Value 的類型與基礎(chǔ)類型相同。如果 HasValue 為 true,則說明 Value 包含有意義的值。如果 HasValue 為 false,則訪問 Value 將引發(fā) InvalidOperationException。
那么我們怎么定義可空類型?
null 的類型可通過下面兩種方式中的一種聲明:
System.Nullable<T> variable
- 或 -
T? variable
T 是可以為 null 的類型的基礎(chǔ)類型。T 可以是包括 struct 在內(nèi)的任何值類型;但不能是引用類型。
現(xiàn)在舉一個例子,運用一下看看效果是不是一樣。
Console.WriteLine("========可空類型操作演示========\n");
Console.WriteLine("\n=========Nullable<T>===========\n");
Nullable<int> x = 5;
Nullable<bool> y = false;
Nullable<double> z = 5.20;
Nullable<char> n = null;
Console.WriteLine("x.HasValue={0}, x.Value={1}",x.HasValue,x.Value);
Console.WriteLine("y.HasValue={0}, y.Value={1}", y.HasValue, y.Value);
Console.WriteLine("z.HasValue={0}, z.Value={1}", z.HasValue, z.Value);
Console.WriteLine("n.HasValue={0}, n.Value={1}",n.HasValue, n.GetValueOrDefault());
Console.WriteLine("\n============== T? ============\n");
int? X = 5;
bool? Y = false;
double? Z = 5.20;
char? N = null;
int?[] arr ={1,2,3,4,5};//一個可空類型的數(shù)組
Console.WriteLine("X.HasValue={0}, X.Value={1}", X.HasValue,X.Value);
Console.WriteLine("y.HasValue={0}, Y.Value={1}", Y.HasValue, Y.Value);
Console.WriteLine("Z.HasValue={0}, Z.Value={1}", Z.HasValue, Z.Value);
Console.WriteLine("N.HasValue={0}, N.Value={1}", N.HasValue, N.GetValueOrDefault());
Console.WriteLine("\n================================\n");
Console.ReadKey();
可空類型可強制轉(zhuǎn)換為常規(guī)類型,方法是使用強制轉(zhuǎn)換來顯式轉(zhuǎn)換或者通過使用 Value 屬性來轉(zhuǎn)換。從普通類型到可以為 null 的類型的轉(zhuǎn)換是隱式的。例如:
int? a = 5;//int--->int?
double? b = a; //int?---->double?
int? c = (int?)b;//double?---int?
int d = (int)c;//int?---->int 不能隱式轉(zhuǎn)換例如int d=c;則不能編譯
int? e = null;
int f = e.Value;//可以編譯但是會提示異常引發(fā) InvalidOperationException
可空類型還可以使用預(yù)定義的一元和二元運算符(提升運算符),以及現(xiàn)有的任何用戶定義的值類型運算符。如果操作數(shù)為 null,這些運算符將產(chǎn)生一個 null 值;否則運算符將使用包含的值來計算結(jié)果。例如:
int? a = 10;
int? b = null;
//一元操作符(+ ++ -- = - ! ~)
a++; //a=11;
//二元操作符(+ - * / % & | ^ << >>)
a *= 10; //a=110;
//a = a + b; //now a is null
//相等性操作符(== !=)
if (b == null)
{
b=b.GetValueOrDefault();
}
Console.WriteLine(a.Value);
a = a + b;
/* if(a == null) ...
* if(b == null) ...
* if(a != b) ... */
//比較操作符
if (a > b)
{
Console.WriteLine("a>b");
}
下面總結(jié)下C#如何對操作符的用法:
1. 一元操作符(+ ++ - -- ! ~)。如果操作數(shù)為null,結(jié)果為null。
2. 二元操作符(+ - * / % | ^ << >>)。兩個操作數(shù)中任何一個為null,結(jié)果為null。
3. 相等性操作符(== !=)。如果兩個操作數(shù)都為null,兩者相等。如果一個操作數(shù)為null,則兩者不相等。如果兩個操作數(shù)都不為null,就對值進(jìn)行比較,判斷它們是否相等。
4. 比較操作符(< > <= >=)。兩個操作數(shù)中任何一個為null,結(jié)果為false。如果兩個操作數(shù)都不為null,就對值進(jìn)行比較。
至此我在對上面代碼的a=a+b解釋一下,它實際等價于:
a = a.HasValue && b.HasValue ? a.Value + b.Value : (int?)null;
在操縱可空實例時,會生成大量代碼,如以下方法:
privatestaticint? NullableCodeSize(int? a, int? b)
{
return a + b;
}
編譯這個方法時,編譯器生成的IL代碼等價于以下的C#代碼:
privatestatic Nullable<int> NullableCodeSize(Nullable<int> a, Nullable<int> b)
{
Nullable<int> nullable1 = a;
Nullable<int> nullable2 = b;
if(!(nullable1.HasValue & nullable2.HasValue))
returnnew Nullable<int>();
else
returnnew Nullable<int>(nullable1.GetValueOrDefault() + nullable2.GetValueOrDefault());
}
??運算
假如左邊的操作數(shù)不為null,就返回這個操作數(shù)的值。如果左邊的操作數(shù)為null,就返回右邊的操作數(shù)的值。利用空接合操作符,可方便地設(shè)置變量的默認(rèn)值??战雍喜僮鞣囊粋€好處在于,它既能用于引用類型,也能用于可空值類型。如下所示:
//===========可空類型=========
int? b =null;
int a = b ??520;
等價于:
//a = b.HasValue ? b.Value : 520
Console.WriteLine(x); //print:"520"
//===========引用類型=========
String s = GetstringValue();
String s= s ??"Unspecified";
等價于:
//String s = GetstringValue();
//filename = (s != null) ? s : "Unspecified";
裝箱和拆箱轉(zhuǎn)換
假定有一個Nullable<int>變量,它被邏輯上設(shè)為null。假如將這個變量傳給一個方法,而該方法期望的是一個object,那么必須對這個變量執(zhí)行裝箱,并將對已裝箱的Nullable<int>的一個引用傳給方法。但并不是一個理想的結(jié)果,因為方法現(xiàn)在是作為一個非空的值傳遞的,即使Nullable<int>變量邏輯上包含null值。為解決這個問題,CLR會在對一個可空變量裝箱的時候執(zhí)行一些特殊代碼,以維護可空類型在表面上的合法地位。
具體地說,當(dāng)CLR對一個Nullable<T>實例進(jìn)行裝箱時,它會檢查它是否為null。如果是,CLR實際就不進(jìn)行任何裝箱操作,并會返回null值。如果可空實例不為null,CLR就從可空實例中取出值,并對其進(jìn)行裝箱。也就是說,一個值為5的Nullable<int>會裝箱成值為5的一個已裝箱Int32。如下所示:
//對Nullable<T>進(jìn)行裝箱,要么返回null,要么返回一個已裝箱的T
int? n =null;
object o = n; //o為null
Console.WriteLine("o is null={0}", o ==null); //"true"
n =5;
o = n; //o引用一個已裝箱的int
Console.WriteLine("o's type={0}", o.GetType()); //"System.Int32"
CLR允許將一個已裝箱的值類型T拆箱為一個T,或者一個Nullable<T>。假如對已裝箱值類型的引用是null,而且要把它拆箱為Nullable<T>,那么CLR會將Nullable<T>的值設(shè)為null。以下代碼對這個行為進(jìn)行了演示:
//創(chuàng)建一個已裝箱的int
object o =5;
//把它拆箱為一個Nullable<int>和一個int
int? a = (int?)o; //a=5
int b = (int)o; //b=5
//創(chuàng)建初始化為null的一個引用
o =null;
//把它“拆箱”為一個Nullable<int>和一個int
a = (int?)o; //a=null;
b = (int)0; //拋出NullReferenceException
將一個值類型拆箱為值類型的一個可空的版本時,CLR可能必須分配內(nèi)存。這是極其特殊的一個行為,因為在其他所有情況下,拆箱永遠(yuǎn)不會導(dǎo)致內(nèi)存的分配。原因在于一個已裝箱的值類型不能簡單的拆箱為值類型的可空版本,在已裝箱的值類型中并不包含 hasValue字段,故在拆箱時CLR必須分配一個Nullable< T>對象,以初始化hasValue = true ,value = 值類型值。
調(diào)用接口方法
下面的代碼中,將一個Nullable<int>類型的變量n轉(zhuǎn)型為一個IComparable<int>,也就是一個接口類型。然而,Nullable<T>不像int那樣實現(xiàn)了IComparable<int>接口。C#編譯器允許這樣的代碼通過編譯,且CLR的校驗器會認(rèn)為這樣的代碼是可驗證的,從而允許我們使用這種更簡潔的語法:
int? n =5;
int result = ((IComparable)n).CompareTo(5); //能順利編譯和運行
Console.WriteLine(result);
如果CLR沒有提供這一特殊支持,那就必須對已拆箱的值類型進(jìn)行轉(zhuǎn)型,然后才能轉(zhuǎn)型成接口以發(fā)出調(diào)用:
int result = ((IComparable)(int)n).CompareTo(5);
可以使用 C# typeof 運算符來創(chuàng)建表示可以為 null 的類型的 Type 對象:
System.Type type = typeof(int?);
還可以使用 System.Reflection 命名空間的類和方法來生成表示可以為 null 的類型的 Type 對象。但是,如果您試圖使用 GetType 方法或 is 運算符在運行時獲得可以為 null 的類型變量的類型信息,得到的結(jié)果是表示基礎(chǔ)類型而不是可以為 null 的類型本身的 Type 對象。
如果對可以為 null 的類型調(diào)用 GetType,則在該類型被隱式轉(zhuǎn)換為 Object 時將執(zhí)行裝箱操作。因此,GetType 總是返回表示基礎(chǔ)類型而不是可以為 null 的類型的 Type 對象。
相關(guān)文章
Unity中的靜態(tài)批處理和動態(tài)批處理操作
這篇文章主要介紹了Unity中的靜態(tài)批處理和動態(tài)批處理操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
C#將Word或Excel文檔轉(zhuǎn)換為Html文件
這篇文章介紹了C#將Word或Excel文檔轉(zhuǎn)換為Html文件的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
C#實現(xiàn)數(shù)據(jù)去重的方式總結(jié)
這篇文章主要來和大家一起來討論一下關(guān)于C#數(shù)據(jù)去重的常見的幾種方式,每種方法都有其特點和適用場景,感興趣的小伙伴可以了解一下2023-07-07

