C#復習筆記(3)--C#2:解決C#1的問題(可空值類型)


可空值類型

C#2推出可空類型來表示可以為null的值類型。這是一個呼聲很高的需求,因為在常用的數據庫中都是允許某些值類型可為空的。那么為什么值類型就不能為空呢?內存中用一個全0的值來表示null,但是全0的地址說明了這個內存空間是被清除了的。所以對象選擇用這種方式來初始化。用byte類型來舉個例子:byte類型用8位來表示一個值,也就是說byte類型可以表示的數據最多是256個(2的8次方)。這256個值中的每一個值都是有用的,我們不可能吧其中一個值永遠的舍棄掉從而來表示一個null值。對於引用類型,引用類型的變量的值在內存中在特定的操作系統中是固定的,32位是4字節,64位是8字節,32位的地址能最多表示42億個對象,就是說這個能表示的地址太多了,所以,可以將一個全0的地址單獨拿出來作為null值來用,因為地址多得永遠都用不完。

先來看一下C#1中還沒有可空值類型的時候是如何解決這些需求的。

方法1:使用一個魔值。確切的來說,是使用一個特定值來表示一個空值,比如DateTime.MinValue,但是這個辦法很牽強,沒有很好的說服力,它只是一個實現可控值類型的權宜之計。

方法2:引用類型的包裝,值類型的優點之一是不需要GC去回收,把一個值類型包裝成一個引用類型,GC的開銷問題沒有很好的解決。同時,值類型同引用類型的轉換會造成裝箱和拆箱,雖然C#2的泛型能夠解決這個問題,但是開頭說的垃圾回收沒有解決。

方法3:用一個全新的值類型來表示可空值類型,這個值類型中包含一個bool值來表示是否包含一個真正的值,並包含一個表示這個值的屬性。方法3正是C#2中做出的最終的決定:Nullable<T>,可空值類型。

一、定義

可空類型的核心部分是System. Nullable< T>。 除此之外,靜態 類 System. Nullable 提供了一些工具方法, 可以簡化可空類型的使用。

先來看一下定義:

 //
    // 摘要:
    //     Represents a value type that can be assigned null.
    //
    // 類型參數:
    //   T:
    //     The underlying value type of the System.Nullable`1 generic type.
    public struct Nullable<T> where T : struct
    {
        //
        // 摘要:
        //     Initializes a new instance of the System.Nullable`1 structure to the specified
        //     value.
        //
        // 參數:
        //   value:
        //     A value type.
        public Nullable(T value); // // 摘要: // Gets a value indicating whether the current System.Nullable`1 object has a valid // value of its underlying type. // // 返回結果: // true if the current System.Nullable`1 object has a value; false if the current // System.Nullable`1 object has no value. public bool HasValue { get; } // // 摘要: // Gets the value of the current System.Nullable`1 object if it has been assigned // a valid underlying value. // // 返回結果: // The value of the current System.Nullable`1 object if the System.Nullable`1.HasValue // property is true. An exception is thrown if the System.Nullable`1.HasValue property // is false. // // 異常: // T:System.InvalidOperationException: // The System.Nullable`1.HasValue property is false. public T Value { get; } // public override bool Equals(object other); // // 摘要: // Retrieves the hash code of the object returned by the System.Nullable`1.Value // property. // // 返回結果: // The hash code of the object returned by the System.Nullable`1.Value property // if the System.Nullable`1.HasValue property is true, or zero if the System.Nullable`1.HasValue // property is false. public override int GetHashCode(); // // 摘要: // Retrieves the value of the current System.Nullable`1 object, or the object's // default value. // // 返回結果: // The value of the System.Nullable`1.Value property if the System.Nullable`1.HasValue // property is true; otherwise, the default value of the current System.Nullable`1 // object. The type of the default value is the type argument of the current System.Nullable`1 // object, and the value of the default value consists solely of binary zeroes. public T GetValueOrDefault(); // // 摘要: // Retrieves the value of the current System.Nullable`1 object, or the specified // default value. // // 參數: // defaultValue: // A value to return if the System.Nullable`1.HasValue property is false. // // 返回結果: // The value of the System.Nullable`1.Value property if the System.Nullable`1.HasValue // property is true; otherwise, the defaultValue parameter. public T GetValueOrDefault(T defaultValue); // // 摘要: // Returns the text representation of the value of the current System.Nullable`1 // object. // // 返回結果: // The text representation of the value of the current System.Nullable`1 object // if the System.Nullable`1.HasValue property is true, or an empty string ("") if // the System.Nullable`1.HasValue property is false. public override string ToString(); public static implicit operator T? (T value); public static explicit operator T(T? value); }
  • 包含一個接受一個參數的構造函數,這個構造函數傳入一個非可空值進行初始化,例如:Nullable<int> a=new Nullable<int>(5);
  • 包含一個HasValue屬性來指示是否包含一個真正的值。
  • 如果HasValue返回true,可以使用Value屬性來返回這個值。如果沒有判斷HasValue直接使用Value,可能會得到一個System.InvalidOperationException的異常。
  • Nullable是不易變的,一般來說,值類型都是不易變的,也就是說已經創建就不會再改變。值類型的復制方式就是復制一個副本,這個副本會在內存形成一個新的地址。
  • GetValueOrDefault()可以返回一個值(如果有)或者返回一個null(如果沒有),它有重載的方法,向內部傳入一個字面量會在沒有真正的值存在的情況下拿這個傳入的字面量當作一個默認值。
  • 其他的方法都是重寫了object里面的。

最后, 框架提供了兩個轉換。 首先, 是T到Nullable< T> 的隱式轉換。 轉換結果為一個HasValue屬性為true的實例。 同樣,Nullable<T> 可以顯式轉換為T, 其作用與Value屬性相同, 在沒有真正的值可供返回時將拋出一個異常。

包裝( wrapping) 和 拆 包( unwrapping)--- 將T的實例轉換成Nullable<T> 的實例的過程在C#語言規范中稱為包裝, 相反的過程則稱為拆包。 在C#語言規范中, 分別是在涉及“ 接受一個參數的構造函數” 和 Value 屬性時定義的這兩個術語。

二、Nullable<T>的裝箱和拆箱

它是值類型,所以會涉及到裝箱和拆箱。

裝箱:Nullable<T>的實例要么裝箱成空引用(如果沒有值),要么裝箱成T的一個已裝箱值。不存在“裝箱的可空int”。

 Nullable<int> nullable = 5;
 object boxed = nullable; Console.WriteLine(boxed.GetType());//這里會輸出System.Int32,印證了不存在“裝箱的可空int”。

 

拆箱:已裝箱的值要么拆箱成一個普通類型,要不拆箱成對應的可空類型。拆箱一個空引用時,如果拆箱成普通類型,會拋出System.NullReferenceException:

 Nullable<int> a = new Nullable<int>();
 object b = a; int c = (int) b;//System.NullReferenceException

但如果拆箱成恰當的可空值類型,則會產生一個沒有值的實例。下面的則不會拋出錯誤:

 Nullable<int> a = new Nullable<int>();
 object b = a; var c = (Nullable<int>) b;

三、Nullable<T>實例的相等性

Nullable< T> 覆蓋了object.Equals( object),注意這個方法需要傳入一個object的類型的實例,所以,傳入的Nullable會進行裝箱。Nullable裝箱的原理上面有描述。這個方法基本與我們期望的一致,沒有什么大的問題。

四、來自非泛型Nullable類的支持

Nullable有兩個方法:

        public static int Compare<T>(T? n1, T? n2) where T : struct;
        public static bool Equals<T>(T? n1, T? n2) where T : struct;

Compare是用Compare<T>.Default來比較兩個的大小:

 Nullable<int> a = 5;
            Nullable<int> b = 5;          
            var defaults = Comparer<int?>.Default;
            Console.WriteLine(defaults.Compare(a,b));//0

Equals使用EqualityComparer< T>. Default。

對於沒有值的實例, 上述每個方法返回的值都遵從.NET的約定:空值與空值相等, 小於其他所有值。

 

五、語法糖

?。使用這個修飾符放到int后面,int?的含義與Nullable<int>的含義完全一樣,包括生成的IL。就像int和System.Int32換着用一樣。

與null值的比較

可以將null賦給一個可空值類型的實例,表示一個沒有真實值的可空值類型。

int? a = null;

可空值類型和其基礎類型的一些匹配的行為

標題中提到的基礎類型是這么一個概念:int?的基礎類型就是int。

假如一個非可空的值類型支持一個操作符或者一種轉換,而且那個操作符或者轉換只涉及其他非可空的值類型時, 那么可空的值類型也支持相同的操作符或轉換。 下面舉一個更具體的例子。 我們知道,int到long存在着一個隱式轉換, 這就意味着 int? 到 long? 也存在一個隱式轉換, 其行為可想而知。

因為int和long存在這么一種隱式轉換的規則,那么,就存在:

int?到long?的隱式轉換

int到long?的隱式轉換

int?到long的顯示轉換。

int a = 1;
long? b = a;//隱式轉換
int? c = 1;
long? d = c;//隱式轉換
int? e = 1;
long f =(long) e;//顯示的轉換

 

轉換和操作符

將基礎類型的一些轉換行為應用到對應的可空類型上的轉換行為叫做提升轉換,將基礎類型的一些操作符的行為應用到相應可空值類型的操作符叫做提升操作符。基礎類型重載操作符后,對應的可空類型就可享用這個重載。

這里的只是還需要從書中進行補充,未完待續。。。。

空合並操作符

這個二元操作符在對 first ?? second 求值 時, 大致會經歷以下步驟:

1、對first進行求值

2、如果結果非空,表達式的值和類型就已經確定

3、如果結果為空,繼續計算second的值並將結果的值作為整個表達式的值。

 int? a = null;
 int c = a ?? 1;

很明顯這里涉及到了類型轉換的問題,我們可以直接將int類型的c變量賦給這個表達式,因為second是非可空的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM