C#裝箱和拆箱的原理介紹
我們知道,值類型的變量是在堆棧上分配內(nèi)存的,而引用類型包括System.Object的對(duì)象是在堆上分配內(nèi)存的,基于這一特點(diǎn),當(dāng)值類型被類型轉(zhuǎn)換時(shí),會(huì)在堆棧和堆上進(jìn)行一系列的操作,這就是裝箱和拆箱的來(lái)源。充分理解裝箱和拆箱,有助于程序員編寫高效率的代碼。
1、裝箱和拆箱的基本概念
我們知道,所有的值類型都繼承自System.ValueType,而System.ValueType繼承自System.Object。所有的值類型對(duì)象都分配在堆棧上,而所有的引用類型包括System.Object對(duì)象都分配在堆上。問(wèn)題隨之而來(lái),既然System.Object是所有值類型的基類,那所有的值類型必然都可以隱式的轉(zhuǎn)換成System.Object類型,此時(shí)這個(gè)對(duì)象會(huì)被放在哪里呢,堆棧上面還是堆上面?實(shí)際上,當(dāng)這個(gè)轉(zhuǎn)換發(fā)生時(shí),CLR需要做額外的工作把堆棧上的值類型移動(dòng)到堆上,這個(gè)操作就被稱為裝箱。來(lái)看一個(gè)裝箱所需要的詳細(xì)步驟。
- 在堆上分配一個(gè)內(nèi)存空間,大小等于需要裝箱的值類型對(duì)象的大小加上兩個(gè)引用類型對(duì)象都擁有的成員:類型對(duì)象指針和同步塊引用。
- 把堆棧上的值類型對(duì)象復(fù)制到堆上新分配的對(duì)象。
- 返回一個(gè)指向堆上新對(duì)象的引用,并且存儲(chǔ)到堆棧上被裝箱的那個(gè)值類型的對(duì)象里。
這些步驟都不需要程序員自己編寫,在任何出現(xiàn)裝箱的地方,編譯器會(huì)自動(dòng)地加上執(zhí)行以上功能的中間代碼。下圖展示了裝箱前后堆和堆棧的變化。

理解了裝箱之后,就可以很方便地理解拆箱操作了。所謂的拆箱,就是裝箱操作的反操作,把堆中的對(duì)象復(fù)制到堆棧中,并且返回其值。需要注意的是,拆箱操作將判斷被拆箱的對(duì)象類型和將要被復(fù)制的值類型引用是否一致,如果不一致,將會(huì)拋出一個(gè)InvalidCastException的異常。這里的類型匹配并不采用任何顯示的類型轉(zhuǎn)換。下面的代碼展示了這一特性。
static void Main(string[] args)
{
try
{
Int32 i = 3;
// 裝箱
Object o = i;
// 拆箱,類型轉(zhuǎn)換失敗
Int16 j = (Int16)o;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Int32 ii = 3;
// 裝箱
Object obj = ii;
// 拆箱
Int16 jj = (Int16)(Int32)obj;
Console.WriteLine("拆箱成功!");
Console.ReadKey();
}程序運(yùn)行結(jié)果:

分析上面的代碼,在第一組裝箱拆箱操作中,代碼試圖把一個(gè)原來(lái)類型為Int32的值類型裝箱后的對(duì)象拆箱成Int16的變量,這樣的拆箱是非法的,運(yùn)行時(shí)會(huì)拋出一個(gè)InvalidCastException的異常。而在第二組裝箱拆箱操作中,就進(jìn)行了正確的類型匹配,拆箱順利完成。
2、裝箱和拆箱對(duì)性能的影響,以及如何避免裝箱拆箱
裝箱和拆箱都意味著堆和堆??臻g的一系列操作,毫無(wú)疑問(wèn),這些操作的性能代價(jià)是很大的,尤其對(duì)于堆上空間的操作,速度相對(duì)于堆棧的操作慢的多,并且可能引發(fā)垃圾回收,這些都將大規(guī)模地影響系統(tǒng)的性能。如何避免裝箱拆箱操作,是程序員在編寫代碼時(shí)需要時(shí)刻考慮的一個(gè)問(wèn)題。裝箱和拆箱操作常發(fā)生在以下兩個(gè)場(chǎng)合:
- 值類型的格式化輸出。
- System.Object類型的容器。
第一種情況,值類型的格式化輸出往往會(huì)涉及一次裝箱操作。例如下面的兩行代碼:
int i = 10;
Console.WriteLine("i的值是:" + i);代碼完全能夠通過(guò)編譯并且正確執(zhí)行,但卻引發(fā)了一次不必要的裝箱操作。在第2行代碼上,值類型i被作為一個(gè)System.Object對(duì)象傳入方法之中,這樣的操作完全可以通過(guò)下面的改動(dòng)來(lái)避免:
int i = 10;
Console.WriteLine("i的值是:" + i.ToString());改動(dòng)后的代碼調(diào)用了i的ToString()方法來(lái)得到一個(gè)字符串對(duì)象。由于字符串是引用類型,所以改動(dòng)后的代碼就不在涉及裝箱操作。
第二種情況更為常見(jiàn)一些。例如常用的容器類ArrayList,就是一個(gè)典型的System.Object容器。任何值類型被放入ArrayList的對(duì)象中,都會(huì)引發(fā)一次裝箱操作。而對(duì)應(yīng)的,取出值類型對(duì)象就會(huì)引發(fā)一次拆箱操作。在.NET 1.1之前,這樣的操作很難避免,但在.NET 2.0推出了泛型的概念后,這些問(wèn)題得到了有效的解決。泛型允許定義針對(duì)某個(gè)特定類型(包括值類型)的容器,并且有效的避免裝箱和拆箱。
三、總結(jié)
裝箱和拆箱本質(zhì)上是值類型在轉(zhuǎn)換到System.Object時(shí)引發(fā)的堆棧和堆的一系列移動(dòng)操作。裝箱時(shí)值類型從堆棧上被復(fù)制到堆上,而拆箱時(shí)從堆上復(fù)制到堆棧上。裝箱和拆箱對(duì)性能有比較大的影響,應(yīng)該避免任何沒(méi)有必要的裝箱和拆箱操作。
在可以確定類型的情況下應(yīng)該使用泛型技術(shù)而避免使用針對(duì)System.Object類型的容器,這樣可以有效避免大規(guī)模地使用裝箱和拆箱操作。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
c#使用ManagedWifi查看當(dāng)前Wifi信號(hào)并選擇wifi的示例
這篇文章主要介紹了c#使用ManagedWifi查看當(dāng)前Wifi信號(hào)并選擇wifi的示例,需要的朋友可以參考下2014-04-04
C#使用Fleck實(shí)現(xiàn)創(chuàng)建WebSocket服務(wù)器
這篇文章主要為大家詳細(xì)介紹了C#如何使用Fleck實(shí)現(xiàn)創(chuàng)建WebSocket服務(wù)器,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01
Winform窗體縮放下使用剪切板功能出現(xiàn)頁(yè)面閃動(dòng)解決分析
這篇文章主要介紹了Winform窗體縮放下使用剪切板功能出現(xiàn)頁(yè)面閃動(dòng)解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
C#中利用Lotus notes公共郵箱發(fā)送郵件的方法
這篇文章主要給大家介紹了關(guān)于C#中利用Lotus notes公共郵箱發(fā)送郵件的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2018-02-02
winform 使用Anchor屬性進(jìn)行界面布局的方法詳解
這篇文章主要介紹了winform 使用Anchor屬性進(jìn)行界面布局的方法,有需要的朋友可以參考一下2013-12-12
避免在C#循環(huán)中使用await的方法小結(jié)
在C#中,異步編程因其能夠提升應(yīng)用程序性能和響應(yīng)能力而變得越來(lái)越流行,async和await關(guān)鍵字使得編寫異步代碼變得更加容易,但如果使用不當(dāng),它們也可能引入一些陷阱,所以本文我們將探討為什么應(yīng)該避免在C#循環(huán)中使用await,并討論一些更高效地處理異步操作的替代方法2024-09-09
Unity UGUI教程之實(shí)現(xiàn)滑頁(yè)效果
使用UGUI提供的ScrollRect和ScrollBar組件實(shí)現(xiàn)基本滑動(dòng)以及自己控制每次移動(dòng)一頁(yè)來(lái)達(dá)到滑頁(yè)的效果。具體實(shí)現(xiàn)思路請(qǐng)參考下本教程2016-04-04

