.Net框架程序設計-讀書筆記(第五章 基元類型、引用類型和值類型)

1. 基元類型、引用類型和值類型
1.1. 基元類型
編譯器直接支持的類型稱爲基元類型(primitive type)。基元類型和.NET框架類庫中的類型有直接的映射關係。
1)       編譯器能夠在基元類型之間進行隱式或者顯式的轉型;
2)       基元類型能夠以文本常量(literals)的形式出現;
3)       如果表達式中含有文本常量,編譯器能夠在編譯時計算該表達式,以提高代碼性能;
4)       編譯器會自動解析出現在代碼中的操作符;
CLR只在32位和64位值上進行算術運算。C和C++不把溢出認爲是一種錯誤,並且允許對發生溢出的值做繞回(wrap)處理。而VB則把溢出視爲一種錯誤。CLR提供IL指令允許編譯器選擇自己期望的行爲。C#允許開發人員自己決定應該如何處理溢出。
1)       CLR提供一類指令,在執行運算時做溢出檢查;
2)       C#編譯器使用/checked+命令開關,checked和unchecked操作符,checked和unchecked語句,來執行溢出檢查;
3)       checked、unchecked操作符和語句都不會影響其中調用方法的行爲;
Decimal是一種非常特殊的類型,CLR沒有直接操作Decimal值的IL指令。
1.2. 引用類型和值類型
CLR支持兩種類型:引用類型和值類型。引用類型實例總是從託管堆上分配,而值類型實例通常在線程的堆棧上分配。在託管代碼中,類型決定了類型實例的分配位置,而使用類型的開發人員對此沒有控制權。任何被稱爲“類”的類型都是引用類型,“結構”或者“枚舉”爲值類型。
引用類型:
1)       內存必須從託管堆中分配;
2)       每個在託管堆中分配的對象,都有一些與之相關的額外附加成員必須被初始化;
3)       從託管堆中分配的對象可能會導致執行垃圾收集;
值類型
1)       CLR不允許一個值類型被用作任何其它引用類型或值類型的基類型,但可以爲一個值類型實現一個或多個接口;
2)       C#允許不使用new關鍵字生成值類型實例,區別是如果使用了new關鍵字,那麼C#認爲實例已經得到了初始化,否則C#會確保值類型的所有字段都被置爲0;
引用類型和值類型的區別:
1)       值類型對象有兩種表示:未裝箱形式和裝箱形式,而引用類型總是裝箱形式;
2)       當定義自己的值類型時,應該重寫Equals方法和GetHashCode方法;
3)       值類型中不可以有任何的抽象方法,不可以引入任何新的虛方法,所有的方法都隱含爲sealed方法;
4)       當一個引用類型變量被創建時,它被初始化爲null。值類型變量總是包含一個符合它的類型的值;
5)       當將一個值類型變量賦值給另一個值類型變量,會進行一個字段對字段的拷貝,而將一個引用類型變量賦值給另一個引用類型變量時,只會拷貝內存地址;
6)       兩個或多個引用類型變量可以指向託管堆中的同一個對象,而每個值類型變量都有一份自己的對象數據拷貝;
7)       值類型實例在內存回收時不可能收到任何通知;
System.Runtime.InteropServices.StructLayout特性用於指示CLR是按指定的順序來存儲類型實例的字段,還是以任何CLR認爲合適的順序排列字段。C#編譯器爲引用類型選擇的是LayoutKind.Auto方式,而爲值類型選擇的是LayoutKind.Sequential方式。
1.3. 值類型的裝箱和拆箱
.NET框架中用來將值類型轉換爲引用類型的機制稱爲裝箱(boxing)。裝箱操作由以下幾步組成:
1)       從託管堆中爲新生成的引用類型對象分配內存;
2)       將值類型實例的字段拷貝到託管堆上新分配的內存中;
3)       返回託管堆中新分配的對象的地址;
拆箱操作由以下幾步組成:
1)       如果該引用爲null,將會拋出一個NullReferenceException異常;
2)       如果該引用指向的對象不是一個期望的值類型的已裝箱對象,就將會拋出一個InvalidCaseException異常;
3)       一個指向包含在已裝箱對象中值類型部分的指針被返回;
拆箱操作僅僅是獲取指向對象中包含的值類型部分的指針而已,它不會拷貝任何字段。但緊接着拆箱操作之後的操作往往是字段拷貝,這兩個操作合起來與裝箱操作才成爲真正的互反操作。
public statics void Main(){
    Int32 v = 5;
    Object o = v;                                                 
    v = 123;
Console.WriteLine(v + “, “ + (Int32)o);                       
①對v進行裝箱。②中,首先v被裝箱,接着o被拆箱,並用一個臨時的Int32值類型變量存放未裝箱部分的拷貝,最後這個臨時變量被裝箱。
1)       因爲未裝箱值類型沒有SyncBlockIndex,所以不能用System.Threading.Monitor類型來同步多個線程對它們的訪問;
2)       因爲未裝箱值類型沒有方法表指針,所以不可能通過值類型的未裝箱實例來調用其上繼承而來的虛方法;
3)       將一個未裝箱的值類型實例轉型爲一個該類型實現的接口類型也需要裝箱;
struct Point : ICloneable{
    public Int32 x, y;
    public override String ToString(){
        return String.Format(“({0}, {1})”, x, y);
    }
    public Object Clone(){ return MemberwiseClone(); }
}
class App{
    static void Main(){
        Point p;
        p.x = 10;
        p.y = 20;
        Console.WriteLine(p.ToString());                         ①
        Console.WriteLine(p.GetType());                         ②
        Point p2 = (Point)p.Clone();                                  ③
        ICloneable c = p2;                                                  ④
        Object o = c.Clone();                                              ⑤
        p = (Point)o;                                                             ⑥
    }
}
上面的代碼中:
1)       正常情況下,調用一個從基類繼承而來的方法,需要一個指向其基類型方法表的指針,但由於Point重寫了ToString方法,而且編譯器知道值類型不會出現多態行爲,因此將產生直接調用ToString的指令;
2)       調用GetType時必須有一個指向方法表的指針,所以需要裝箱;
3)       由於Point重寫了Clone方法,它將產生直接調用Clone的指令,但需要對返回的Object對象進行拆箱;
4)       當p2被轉型到一個接口類型時,p2必須被裝箱;
5)       由於Point重寫了Clone方法,所以c不必被裝箱;
6)       當o被轉型爲Point時,被o引用的對象需要拆箱操作;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章