為什麼有裝箱和拆箱,兩者起到什麼作用?net的所有型別都是由基類system.object繼承過來的,包括最常用的基礎型別:int, byte, short,bool等等,就是說所有的事物都是物件。如果程式中所有的型別操作用的是引用型別時,往往導致效率低下,所以.net通過將資料型別分為值型別和引用型別。
前面文章中講過;
定義:值型別是在棧中分配記憶體,在宣告時初始化後才能使用,不能為null。
a、整型:(sbyte、byte、char、short、ushort、int、uint、long、ulong)
b、浮點型:(float、double)、decima、bool
c、使用者定義的結構(struct)。
定義:引用型別是在託管堆中分配記憶體空間用於儲存資料、資料指標、以及sync等,初始化預設為null。
包括類、介面、委託、陣列以及內建引用型別object與string。
什麼是堆,什麼是棧?
1、堆區(heap) 一般由程式設計師進行申請、釋放,若程式設計師不釋放,在程式退出時記憶體自動釋放。
2、棧區(statck)- 由編譯器自動分配釋放,存放函式的引數值,區域性變數的值
3、全域性區(靜態區)-全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域, 未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。 - 程式結束後由系統釋放
4、文字常量區 —常量字串就是放在這裡的。 程式結束後由系統釋放
5、程式**區—存放函式體的二進位制**。
通俗上講,裝箱是講值型別轉化為引用型別,
拆箱是將引用型別轉化為值型別 。
將值型別與引用型別鏈結起來為何需要裝箱?(為何要將值型別轉為引用型別?)
一種最普通的場景是,呼叫乙個含型別為object的引數的方法,該object可支援任意為型,以便通用。當你需要將乙個值型別(如int32)傳入時,需要裝箱。
另一種用法是,乙個非泛型的容器,同樣是為了保證通用,而將元素型別定義為object。於是,要將值型別資料加入容器時,需要裝箱。
1、首先從託管堆中為新生成的引用物件分配記憶體。
2、然後將值型別的資料拷貝到剛剛分配的記憶體中。
3、返回託管堆中新分配物件的位址。
可以看出,進行一次裝箱要進行分配記憶體和拷貝資料這兩項比較影響效能的操作。
1、首先獲取託管堆中屬於值型別那部分欄位的位址,這一步是嚴格意義上的拆箱。
2、將引用物件中的值拷貝到位於執行緒堆疊上的值型別例項中。
經過這2步,可以認為是同boxing是互反操作。嚴格意義上的拆箱,並不影響效能,但伴隨這之後的拷貝資料的操作就會同boxing操作中一樣影響效能。
c#**
bject objvalue = 4;
intvalue = (int)objvalue;
上面的兩行**會執行一次裝箱操作將整形數字常量4裝箱成引用型別object變數objvalue;然後又執行一次拆箱操作,將儲存到堆上的引用變數objvalue儲存到區域性整形值型別變數value中。
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
上述**中有幾個box意味著有幾次裝箱操作,有幾個unbox就是拆箱操作。
在拆箱中注意的問題:
int x = 0;
int32 y = new int32();
object o ;
o = x; //隱式的裝箱。
o = (int32)y; //顯示的裝箱。
對於裝箱而言,是不存在任何疑問點,既可以用顯示(explicit),也可以用隱式(implicit)。
x = o; //error;
x = (int)o 或者 x = (int32)o; //right;
拆箱必須是顯示的,而不是隱式的。
int32 x = 5;
int64 y = 6;
object
o; o = x; or o = (int32)x;
y = (int64)o; //it's error.
裝箱的型別必須與拆箱的型別一致。而不是什麼可隱式轉換之類的。所以裝箱的時候用的是int32,拆箱的時候必須是int32。
通過泛型來避免。
- 非泛型集合
var array = new arraylist();
array.add(1);
array.add(2);
foreach (int
value
inarray)
」,value);
}
在向arraylist中新增int型別元素時會發生裝箱,在使用foreach列舉arraylist中的int型別元素時會發生拆箱操作,將object型別轉換成int型別,在執行到console.writeline時,還會執行兩次的裝箱操作;這一段**執行了6次的裝箱和拆箱操作;
var list = new list();
list.add(1);
list.add(2);
foreach (int
value
inlist)
", value);
}
**和1中的**的差別在於集合的型別使用了泛型的list,而非arraylist;我們同樣可以通過檢視il**檢視裝箱拆箱的情況,上述**只會在console.writeline()方法時執行2次裝箱操作,不需要拆箱操作。
通過過載函式來避免。
struct a : icloneable
」,x);
} public
object
clone()
} static
void main()
5.0:a.tostring()。編譯器發現a重寫了tostring方法,會直接呼叫tostring的指令。因為a是值型別,編譯器不會出現多型行為。因此,直接呼叫,不裝箱。(注:tostring是a的基類system.valuetype的方法)
5.1:a.gettype(),gettype是繼承於system.valuetype的方法,要呼叫它,需要乙個方法表指標,於是a將被裝箱,從而生成方法表指標,呼叫基類的system.valuetype。(補一句,所有的值型別都是繼承於system.valuetype的)。
5.2:a.clone(),因為a實現了clone方法,所以無需裝箱。
5.3:icloneable轉型:當a2為轉為介面型別時,必須裝箱,因為介面是一種引用型別。
5.4:c.clone()。無需裝箱,在託管堆中對上一步已裝箱的物件進行呼叫。
有時我們可以提前進行裝箱或者拆箱操作
比如:
//當我們如下時:
for (int i = 0; i < arrlist.length; i++)
//我們更因該這樣:
int l = arrlist.length;
for (int i = 0; i < l; i++)
附:其實上面的基於乙個根本的原理,因為未裝箱的值型別沒有方法表指標,所以,不能通過值型別來呼叫其上繼承的虛方法。另外,介面型別是乙個引用型別。對此,我的理解,該方法表指標類似c++的虛函式表指標,它是用來實現引用物件的多型機制的重要依據
凡事並不能絕對,假設你想改造的**為第三方程式集,你無法更改,那你只能是裝箱了。
c 裝箱和拆箱
c 裝箱和拆箱 概念 裝箱 將值型別轉換為引用型別的過程叫做裝箱 值型別 引用型別 相反,拆箱 將引用型別轉換為值型別 叫做拆箱 引用型別 值型別 裝箱例子 int i 2008 object obj i console.writeline 1 i的值為,裝箱之後的值 i,obj i 927 con...
C 裝箱和拆箱
1 什麼是裝箱和拆箱 裝箱是將值型別轉換為引用型別 拆箱是將引用型別轉換為值型別 2 什麼時候需要裝箱?值型別是高效輕量的型別,因為預設情況下在堆上不包括他們的物件元件,然而,如果我們需要物件元件,這個時候就需要裝箱了。最常見的場景就是 乙個方法你希望能共用,設定的引數型別是object型別 引用型...
C 裝箱和拆箱
值型別例項進行裝箱時的步驟 1 在託管堆中分配記憶體。需要注意的是,由於是將值型別進行引用型別化,因而分配的記憶體空間除了值型別各個欄位所需的記憶體之外,還要加上託管堆所有物件都有的兩個額外成員 型別物件指標和同步塊索引 所需的記憶體。2 將值型別的字段複製到新分配的堆記憶體中。3 返回物件位址,即...