目錄:
1. 裝箱和拆箱
2. 深入理解裝箱和拆箱
3. int[] to object[],值類型數(shù)組到對象數(shù)組的轉(zhuǎn)化
4. 使用泛型減少裝箱和拆箱
1. 裝箱和拆箱
裝箱 就是把“值類型”轉(zhuǎn)換成“引用類型”;
拆箱 就是把“引用類型”轉(zhuǎn)換成“值類型”;
首先,我們要弄明白為什么需要裝箱和拆箱。C#的所有類型,包括int、boo等,都繼承自System.Object,但是卻又有值類型和引用類型之分。這時(shí)你要問,int是繼承自object類型的,object是引用類型,那為何int不是引用類型而是值類型的呢?這就涉及到裝箱和拆箱的概念了。
我們知道對象是創(chuàng)建在堆上的,它的創(chuàng)建和銷毀必然帶來額外的CPU和內(nèi)存消耗。如果將int,boo等微小而常用的數(shù)據(jù)類型都放在堆上創(chuàng)建和銷毀,語言的性能將會(huì)被極大的限制,有時(shí)甚至是無法忍受的。C#將值類型和引用類型分開,值類型直接在棧中被創(chuàng)建,超過作用域后直接銷毀。當(dāng)需要值類型成為對象時(shí),使用裝箱操作,讓值類型變?yōu)橐粋€(gè)引用類型的對象。這樣,我們就可以使用object作為通用的接口統(tǒng)一語言內(nèi)的一切類型。
拆箱 在MSDN官方文檔里用的是 取消裝箱。事實(shí)上拆箱是裝箱的逆操作,也就是說我們只對裝過箱的引用類型(通常是object對象)進(jìn)行拆箱操作。單純拆箱操作的后果無法設(shè)想的。
裝箱和拆箱是C#的核心概念,C#利用其完成類型系統(tǒng)的統(tǒng)一。有了裝箱,任何類型的值都可以視為一個(gè)對象。CLR在裝箱時(shí)是將值類型包裝到System.Object的內(nèi)部,再將其存儲(chǔ)到托管堆上。拆箱是從對象中提取值類型。裝箱是隱式的而拆箱是顯示的。
1 2 3 4 5 6 7 8 | //裝箱 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,檢查對象實(shí)例,以卻確保它是給定值類型的裝箱值。
2,將該值從實(shí)例復(fù)制到值類型變量中。
下面來看看這個(gè)例子:
1 2 3 | int i=0;
System.Object obj=i;
Console.WriteLine(i+","+(int)obj);
|
其中共發(fā)生了3次裝箱和一次拆箱!^_^,看出來了吧?! 第一次是將i裝箱,第2次是輸出的時(shí)候?qū)轉(zhuǎn)換成string類型,而string類型為引用類型,即又是裝箱,第三次裝箱就是(int)obj的轉(zhuǎn)換成string類型,裝箱! 拆箱就是(int)obj,將obj拆箱??!
2. 深入理解裝箱和拆箱
這句話的IL代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | .locals init (
[0] object objValue
) //以上三行IL表示聲明object類型的名稱為objValue的局部變量
IL_0000: nop
IL_0001: ldc.i4.s 1 //表示將整型數(shù)1放到棧頂
IL_0003: box [mscorlib]System.Int32 //執(zhí)行IL box指令,在內(nèi)存堆中申請System.Int32類型需要的堆空間
IL_0008: stloc.0 //彈出堆棧上的變量,將它存儲(chǔ)到索引為0的局部變量中
|
注意注釋的部分。執(zhí)行裝箱操作時(shí)不可避免的要在堆上申請內(nèi)存空間,并將堆棧上的值類型數(shù)據(jù)復(fù)制到申請的堆內(nèi)存空間上,這肯定是要消耗內(nèi)存和cpu資源的。
1 2 3 | object objValue = 4;
int value = (int)objValue;
|
同樣,看看IL代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .locals init (
[0] object objValue,
[1] int32 'value'
) //上面IL聲明兩個(gè)局部變量object類型的objValue和int32類型的value變量
IL_0000: nop
IL_0001: ldc.i4.4 //將整型數(shù)字4壓入棧
IL_0002: box [mscorlib]System.Int32 //執(zhí)行IL box指令,在內(nèi)存堆中申請System.Int32類型需要的堆空間
IL_0007: stloc.0 //彈出堆棧上的變量,將它存儲(chǔ)到索引為0的局部變量中
IL_0008: ldloc.0//將索引為0的局部變量(即objValue變量)壓入棧
IL_0009: unbox.any [mscorlib]System.Int32 //執(zhí)行IL 拆箱指令unbox.any 將引用類型object轉(zhuǎn)換成System.Int32類型
IL_000e: stloc.1 //將棧上的數(shù)據(jù)存儲(chǔ)到索引為1的局部變量即value
|
拆箱操作的執(zhí)行過程和裝箱操作過程正好相反,是將存儲(chǔ)在堆上的引用類型值轉(zhuǎn)換為值類型并給值類型變量。裝箱操作和拆箱操作是要額外耗費(fèi)cpu和內(nèi)存資源的,所以在c# 2.0之后引入了泛型來減少裝箱操作和拆箱操作消耗。
3. int[] to object[],值類型數(shù)組到對象數(shù)組的轉(zhuǎn)化
我們不能直接把值類型的數(shù)組賦值給對象數(shù)組,例如:
1 2 | int[] array = new int[] { 0 } ;
object[] oiArray = (object[])array;//error!! 不能將int[] 轉(zhuǎn)換到 object[]
|
1 | string[] a={"1","2","3"};
|
1 | object[] osArray = a ;//正確,a是引用類型數(shù)組,不存在裝箱和拆箱
|
(object[])a無法將a所有的值類型對象“直接”轉(zhuǎn)換為引用類型,所以編譯器不會(huì)通過這個(gè)轉(zhuǎn)換??梢允褂萌缦碌姆绞竭_(dá)到目的:
1 2 3 4 5 6 | int[] array = new int[] { 0 } ;
object[] oArray = new object[array.Length];
for(int i =0 ; i< array.Length ; i++)
{
oArray[i] = array[i]; //隱式裝箱
}
|
4. 使用泛型減少裝箱和拆箱
有時(shí)說使用泛型能提高C#程序的性能,有一部分性能的提升是由減少了裝箱和拆箱帶來的??疾煜旅娴拇a:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class Test
{
object _o ;
public Test(object o)
{
_o = o ;
}
}
public class Test<T>
{
T _o ;
public Test(T o)
{
_o = o ;
}
}
|
第一個(gè)Test類中沒有使用泛型,如果將一個(gè)int類型的值傳入Test,將會(huì)引發(fā)多次的裝箱和拆箱操作。而泛型類在實(shí)例化時(shí)已經(jīng)明確了類型,復(fù)制操作時(shí)就不會(huì)有裝箱和拆箱操作了。
引用:
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
|