實值型別是比參考型別更“輕型”的一種類型,因為它不需要作為對象在託管堆中分配,不會被記憶體回收,也不通過指標來引用。
static void Main()
{
System.Collections.ArrayList list = new System.Collections.ArrayList();
Point p;//分配一個Point,不在堆中分配。
for (int i = 0; i < 20; i++)
{
p.x = p.y = i;//初始化實值型別的成員
list.Add(p);//對實值型別進行裝箱,並將引用添加到ArrayList中。C#編譯器會自動的產生對一個實值型別的執行個體進行裝箱所需的IL代碼,C#編譯器檢測到是向一個需要參考型別的方法傳遞一個實值型別,所以會自動產生代碼對對象進行裝箱。在運行時,當前存在於Point實值型別執行個體p中的欄位會複製到新分配的Point對象中。已裝箱的Point對象(已經是參考型別)的地址會返回給Add方法。Point對象會一直存在於堆中,直到被記憶體回收。Point實值型別的變數p可以重用,因為ArrayList根本不知道關於它的任何事情。
Point p1 = (Point)list[0];//拆箱 ,
}
}
List.Add()方法需要一個object參數,Add需要擷取對託管堆上的一個對象的引用(或指標)來作為參數。在代碼中傳遞的是p,是一個實值型別,為了使代碼正確工作,Point已經進行了一次裝箱。
對一個實值型別的執行個體進行裝箱操作時發生在內部的事情:
1.在託管堆中分配記憶體。
2.實值型別的欄位複製到新分配的堆記憶體中。
3.返回對象的地址,現在這個地址是對一個對象的引用,實值型別現在是一個參考型別。
拆箱發生的事:
1.擷取已裝箱的對象的各個欄位的地址。
2.將欄位包含的值從堆中複製到基於棧的實值型別執行個體中。
拆箱的代價比裝箱的低得多。拆箱就是擷取一個指標的過程,該指標包含在一個對象中的原始實值型別,事實上,指標指向的是已裝箱執行個體中未裝箱的部分,所以和裝箱不同,拆箱不要求在記憶體中複製任何位元組,往往會緊接著拆箱操作發生一次欄位的複製操作。
裝箱和拆箱會對應用程式的速度和記憶體消耗產生不利影響,所以應該注意編譯器在什麼時候產生代碼來自動這些操作,並嘗試手動編寫代碼,盡量避免自動產生代碼的情況。
static void Main()
{
Int32 i = 5;
object obj = i;//裝箱
Int16 n = (Int16)obj;//拆箱失敗,拋出InvalidCastException異常。如果引用指向的對象不是所期待的實值型別的一個已裝箱執行個體,就拋出此異常。
Int16 nn = (Int16)(Int32)obj;//先拆箱為正確的類型,在進行轉型。
Int32 d = (Int32)obj;//
Console.ReadKey();
}
在對一個對象進行拆箱時,只能將其轉型為原先未裝箱時的實值型別。
static void Main()
{
Int32 i = 5;
object obj = i;//裝箱
i = 123;
Console.WriteLine(i + "," + (Int32)obj);//發生了多少次裝箱?WriteLine()需要擷取一個String對象,但是當前沒有String對象,所以採取
//某種方式對這些資料進行合并,建立一個String對象。
//為了建立String對象編譯器產生代碼來調用String對象的靜態方法Concat。
//String.Concat方法有幾個重載版本,所有版本的執行操作都是一樣的,不同的是參數數量。
//這裡串連3個資料項目建立一個字串,所以編譯器選擇的是
//public static String Concat(object obj0,object obj1,object obj2) 這個版本
//為第一個參數傳遞的是i,i是一個未裝箱的實值型別,裝箱。
//obj1是一個String對象的引用。obj3要求一次拆箱操作。然後裝箱。
Console.WriteLine(i.ToString() + "," + obj);//這裡沒有裝箱操作。i調用ToString方法,返回一個String,String對象已經是參考型別,所以能直接傳遞給Concat方法,不需要任何裝箱操作
Console.ReadKey();
}
看看FCL,會發現許多方法都針對不同的實值型別參數進行重載。大多數方法進行重載的唯一目的就是減少常實值型別的裝箱操作次數。
未裝箱的實值型別比參考型別更“輕型”,歸結於以下兩個原因:
1.實值型別不再託管堆上。
2.實值型別沒有堆上的每個對象都有的額外成員:一個類型對象指標和同步塊索引。
因為實值型別沒有同步塊索引,所以哈,不能使用System.Threading.monitor類型的各種方法,實現不了同步。
在C#中只用使用介面才能修改一個已裝箱的實值型別中的欄位,詳情見126頁。