CLR:基元類型、引用類型和值類型
最新更新請訪問: http://denghejun.github.io
前言
今天重新看了下關于CLR基元類型的東西,覺得還是有必要將其記錄下來,畢竟這是理解CLR成功
之路上的重要一步,希望你也和我一樣。
基元類型
編譯器直接支持的數據類型稱之為基元類型,針對那些程序員自定義的類型而言。所有基元類型
直接映射到FCL(Framework class library)中存在的類型;比如C#中int直接映射到System.
Int32類型,且在編譯為IL(中間語言)時,他們將會是一模一樣的:
int a=0; System.Int32 a=0; int a=new int(); System.Int32 a=new System.Int32();
至于,為什么我們經常使用的是int這樣的簡單類型,而不是System.Int32,微軟在C#語言規范
(CLS)中有這樣的建議:“從風格上說,最好是使用關鍵字,而不是使用完整的系統類型名稱”。
但是使用關鍵字有時候會使得程序員倍感迷惑,例如int比較沒有Int32那樣直接的顯示這是一個有
符號的32值。
引用類型
任何稱之為“類”的類型都是引用類型。引用類型總是從堆上分配內存,C#的new操作符將會返回對
象的內存地址。使用引用類型時必須考慮以下事實;
-
- 內存必須從托管堆上分配
- 堆上分配的每個對象都有一些額外的成員,它們必須初始化
- 對象中的其他字節總是設為零
- 從托管堆上分配一個對象時,可能強制執行一次垃圾收集操作
很明顯,過多的使用引用類型可能會導致應用程序性能顯著下降。
引用類型變量的互相賦值只會賦值對象的內存地址,所以指向同一對象的變量在發生改變時實際上影
響的是同一個對象。
值類型
所有值類型又都稱之為結構或枚舉。值類型在線程棧上分配空間。所有的值類型都直接派生于抽象類
型System.ValueTye,而后者本身又直接從System.Object派生。所有值類型都是密封的(sealed)
,因此,無法被繼承,從而無法使用值類型定義新的類型。
值類型變量的互相賦值將會執行一次逐字段的復制。
值類型與引用類型的取舍
將數據類型定義為結構(值類型)需要考慮一下幾點:
-
- 不需要從其他類型繼承
- 不需要派生
- 類型實例較小或不作為實參和返回值
- 類型實例不需要做線程同步訪問
無法繼承和派生是值類型的顯著特點,你必須慎重考慮他們。另外,若值類型實例過大,在入參時會
發生復制行為,占用空間;在作為返回值時也將值類型的實例復制到調用者的分配內存中。因為未裝箱
的值類型沒有同步索引塊,所以不能使用Monitor或lock等方法(語句)讓多個線程同步對這個對象的
訪問。
裝箱與拆箱
值類型有兩種表現形式:未裝箱(unboxed)和已裝箱(boxed)形式;引用類型總是處于已裝箱模
式。
值類型是一種“輕型”的類型,它不會作為對象在托管堆中分配內存,不會被垃圾回收,也不能通過指針
來引用。但在許多情況下我們需要獲取對一個值類型實例的引用:
struct Point { public int x,y; } ArrayList list=new ArrayList(); Point p; for(int i=0;i<5;i++) { p.x=p.y=i; list.Add(p); // 將值類型進行裝箱,并添加到集合中 }
上面的例子中,由于ArrayList的Add方法需要一個類型為Objec的入參,而我們傳入的是值類型Point
,所以這里將發生裝箱的操作。所有在值類型轉化為引用類型的地方都需要裝箱。裝箱(boxing)內部
發生的過程如下:
1.在托管堆上分配好內存,大小為值類型所有字段的大小加上引用類型的額外
成員(對象指針和同步塊索引)
2.值類型的字段復制到新的堆內存中
3.返回對象的地址
可見,已裝箱的值類型的生命周期超過了未裝箱的值類型。
另外,值類型在轉化為某個接口或調用未重寫的基類方法時(所有的值類型都繼承System.Object),需
要裝箱。因為基類的this希望接受一個指向堆上的一個對象的指針。
拆箱并不是裝箱的逆過程:
Point p=(Point)list[0];
拆箱在CLR中分兩步完成這個操作:
1.獲取已裝箱的Point對象中的各個Point字段的地址,這個過程就是拆箱(unboxing);
2.將這些字段包含的值從堆中復制到基于棧的值類型實例中(也就是上例中的p)。
所以,拆箱實際上是指一個尋址的過程,拆箱的代價遠低于裝箱,因為它確實知識一個簡單的尋找指針
的過程而已,在這之后才會發生逐字段復制的過程。

浙公網安備 33010602011771號