1. 裝箱和拆箱
2. 深入理解裝箱和拆箱
3. int[] to object[],值類型數組到對象數組的轉化
4. 使用泛型減少裝箱和拆箱
裝箱 就是把“值類型”轉換成“引用類型”;
拆箱 就是把“引用類型”轉換成“值類型”;
首先,我們要弄明白為什麼需要裝箱和拆箱。C#的所有類型,包括int、boo等,都繼承自System.Object,但是卻又有值類型和引用類型之分。這時你要問,int是繼承自object類型的,object是引用類型,那為何int不是引用類型而是值類型的呢?這就涉及到裝箱和拆箱的概念了。
我們知道對象是創建在堆上的,它的創建和銷毀必然帶來額外的CPU和內存消耗。如果將int,boo等微小而常用的數據類型都放在堆上創建和銷毀,語言的性能將會被極大的限制,有時甚至是無法忍受的。C#將值類型和引用類型分開,值類型直接在棧中被創建,超過作用域後直接銷毀。當需要值類型成為對象時,使用裝箱操作,讓值類型變為一個引用類型的對象。這樣,我們就可以使用object作為通用的接口統一語言內的一切類型。
拆箱 在MSDN官方文檔裡用的是 取消裝箱。事實上拆箱是裝箱的逆操作,也就是說我們只對裝過箱的引用類型(通常是object對象)進行拆箱操作。單純拆箱操作的後果無法設想的。
裝箱和拆箱是C#的核心概念,C#利用其完成類型系統的統一。有了裝箱,任何類型的值都可以視為一個對象。CLR在裝箱時是將值類型包裝到System.Object的內部,再將其存儲到托管堆上。拆箱是從對象中提取值類型。裝箱是隱式的而拆箱是顯示的。
//裝箱 boxing int i = 3 ; //分配在棧上 object o = i ;//隱式裝箱操作,int i 在堆上 object b = (object)i ; //顯示裝箱操作 //拆箱 unboxing int j = (int) o ;//顯示拆箱(將對象o拆箱為int類型) int k = b ;//error!!, 不能隱式拆箱
拆箱 的操作包括
1,檢查對象實例,以卻確保它是給定值類型的裝箱值。
2,將該值從實例復制到值類型變量中。
下面來看看這個例子:
int i=0; System.Object obj=i; Console.WriteLine(i+","+(int)obj);
其中共發生了3次裝箱和一次拆箱!^_^,看出來了吧?!
第一次是將i裝箱,第2次是輸出的時候將i轉換成string類型,而string類型為引用類型,即又是裝箱,第三次裝箱就是(int)obj的轉換成string類型,裝箱!
拆箱就是(int)obj,將obj拆箱!!
object o = 1 ;
這句話的IL代碼如下:
.locals init ( [0] object objValue ) //以上三行IL表示聲明object類型的名稱為objValue的局部變量 IL_0000: nop IL_0001: ldc.i4.s 1 //表示將整型數1放到棧頂 IL_0003: box [mscorlib]System.Int32 //執行IL box指令,在內存堆中申請System.Int32類型需要的堆空間 IL_0008: stloc.0 //彈出堆棧上的變量,將它存儲到索引為0的局部變量中
注意注釋的部分。執行裝箱操作時不可避免的要在堆上申請內存空間,並將堆棧上的值類型數據復制到申請的堆內存空間上,這肯定是要消耗內存和cpu資源的。
object objValue = 4; int value = (int)objValue;
同樣,看看IL代碼:
.locals init ( [0] object objValue, [1] int32 'value' ) //上面IL聲明兩個局部變量object類型的objValue和int32類型的value變量 IL_0000: nop IL_0001: ldc.i4.4 //將整型數字4壓入棧 IL_0002: box [mscorlib]System.Int32 //執行IL box指令,在內存堆中申請System.Int32類型需要的堆空間 IL_0007: stloc.0 //彈出堆棧上的變量,將它存儲到索引為0的局部變量中 IL_0008: ldloc.0//將索引為0的局部變量(即objValue變量)壓入棧 IL_0009: unbox.any [mscorlib]System.Int32 //執行IL 拆箱指令unbox.any 將引用類型object轉換成System.Int32類型 IL_000e: stloc.1 //將棧上的數據存儲到索引為1的局部變量即value
拆箱操作的執行過程和裝箱操作過程正好相反,是將存儲在堆上的引用類型值轉換為值類型並給值類型變量。裝箱操作和拆箱操作是要額外耗費cpu和內存資源的,所以在c# 2.0之後引入了泛型來減少裝箱操作和拆箱操作消耗。
我們不能直接把值類型的數組賦值給對象數組,例如:
int[] array = new int[] { 0 } ; object[] oiArray = (object[])array;//error!! 不能將int[] 轉換到 object[]
string[] a={"1","2","3"};
object[] osArray = a ;//正確,a是引用類型數組,不存在裝箱和拆箱
(object[])a無法將a所有的值類型對象“直接”轉換為引用類型,所以編譯器不會通過這個轉換。可以使用如下的方式達到目的:
int[] array = new int[] { 0 } ; object[] oArray = new object[array.Length]; for(int i =0 ; i< array.Length ; i++) { oArray[i] = array[i]; //隱式裝箱 }
有時說使用泛型能提高C#程序的性能,有一部分性能的提升是由減少了裝箱和拆箱帶來的。考察下面的代碼:
public class Test { object _o ; public Test(object o) { _o = o ; } } public class Test<T> { T _o ; public Test(T o) { _o = o ; } }
第一個Test類中沒有使用泛型,如果將一個int類型的值傳入Test,將會引發多次的裝箱和拆箱操作。而泛型類在實例化時已經明確了類型,復制操作時就不會有裝箱和拆箱操作了。
1. 玉開 http://www.cnblogs.com/yukaizhao/archive/2011/10/18/csharp_box_unbox_1.html
2. MSDN https://msdn.microsoft.com/zh-cn/library/yz2be5wk.aspx