談到裝箱拆箱,DebugLZQ相信給位園子里的博友一定可以娓娓道來(lái),大概的意思就是值類型和引用類型的相互轉(zhuǎn)換唄---值類型到引用類型叫裝箱,反之則叫拆箱。這當(dāng)然沒(méi)有問(wèn)題,可是你只知道這么多,那么DebugLZQ建議你花點(diǎn)時(shí)間看看樓主這篇文章,繼續(xù)前幾篇博文的風(fēng)格--淺談雜侃。
1. .NET中的類型
為了說(shuō)明裝箱和拆箱,那首先必須先說(shuō)類型。在.NET中,我們知道System.Object類型是所有內(nèi)建類型的基類。注意這里說(shuō)的是內(nèi)建類型,程序員可以編寫不繼承子自System.Object的類型,這里不做過(guò)多的介紹(感興趣的博友可以研究一下)。
所有.NET的類型都可以分為兩類(有點(diǎn)不嚴(yán)謹(jǐn),但是大家都這么講):值類型和引用類型。那么值類型和引用類型如何區(qū)分,標(biāo)準(zhǔn)是什么?簡(jiǎn)單也明確的一個(gè)區(qū)分標(biāo)準(zhǔn)是:所有的值類型都繼承自System.ValueType(System.ValueType繼承自System.Object),也就是說(shuō),所有繼承自System.ValueType的類型都是值類型,而其他類型都是引用類型。(題外話:以前在讀一位博友王濤的《你必須知道的.NET》中,他說(shuō),值類型和引用類型本質(zhì)的區(qū)別是:值類型和引用類型在內(nèi)存中分配的位置不同,前者分配在堆棧上,后者分配在堆上。個(gè)人覺(jué)得這個(gè)不是一個(gè)簡(jiǎn)單明確的區(qū)分方法。遠(yuǎn)沒(méi)有DebugLZQ說(shuō)的這么露骨?。?/p>
說(shuō)到這里,你應(yīng)該要有這樣的想法:嚴(yán)格來(lái)說(shuō)的話,System.Object作為所有內(nèi)建類型的基類,本身并沒(méi)有值類型和引用類型之分。但是System.Object的對(duì)象,具有引用類型的特點(diǎn)。這也是值類型在有些場(chǎng)合需要裝箱拆箱的原因。
下面還是簡(jiǎn)單說(shuō)下值類型和引用類型的不一樣的地方吧,分3塊,個(gè)人覺(jué)得理解這3塊就可以了:
變量賦值
值類型的變量將直接獲得一個(gè)真實(shí)的數(shù)據(jù)副本,而對(duì)引用類型的賦值僅僅是吧對(duì)象的引用賦給變量,這樣就可能導(dǎo)致多個(gè)變量引用到一個(gè)實(shí)際對(duì)象實(shí)例上(這里需要各位博友去理解.NET對(duì)String的一些優(yōu)化機(jī)制,本質(zhì)和這個(gè)不相悖)。
內(nèi)存分配
引用類型的對(duì)象將在堆上分配內(nèi)存,而值類型的對(duì)象則會(huì)在堆棧上分配內(nèi)存。(內(nèi)存如何分配:堆棧上存的是什么?值類型變量和引用類型變量的引用。堆上存的是什么?引用類型的對(duì)象(包括了類型對(duì)象指針和同步塊索引,注意只是個(gè)索引,這是.NET為線程同步提出的一種折中的辦法。))。大對(duì)象堆(也是堆,一種特別的堆)什么的這里不做介紹。但必須說(shuō)明的是:堆棧的空間有限,但運(yùn)行效率卻比堆要高得多?。?!
由于所有的值類型都繼承自System.ValueType,而System.ValueType繼承自System.Object,并重新實(shí)現(xiàn)了基類System.Object的一個(gè)虛方法Equals,而引用類型并沒(méi)有重寫。
2.裝箱拆箱原理
前面簡(jiǎn)單介紹了.NET中的類型,下面引入裝箱和拆箱。通過(guò)1我們知道值類型的對(duì)象是在堆棧上分配內(nèi)存的,而引用類型(包括System.Object)對(duì)象是在堆上分配內(nèi)存的,那么當(dāng)值類型被類型轉(zhuǎn)換時(shí),會(huì)在堆棧和堆上進(jìn)行一系列的操作,這就是裝箱拆箱的來(lái)源。
充分理解裝箱拆箱的原理,有助于我們程序員寫出高效的代碼。
梳理下:前面DebugLZQ說(shuō)到,所有值類型都繼承自System.ValueType,而Sytem.ValueType繼承自System.Object;所有值類型對(duì)象都分配在堆棧上,而所有引用類型,當(dāng)然包括System.Object,對(duì)象都分配在堆上。那么,問(wèn)題來(lái)了:既然System.Object 是所有值類型的基類,那么所有值類型必然可以隱式轉(zhuǎn)換成System.Object(面向?qū)ο笾械念愋吞鎿Q原則,基類能夠替換子類),那么這個(gè)對(duì)象將被分配在哪里,堆上還是堆棧上?事實(shí)上,當(dāng)這個(gè)轉(zhuǎn)換發(fā)生時(shí),CLR需要做額外的工作把堆棧上的值類型移動(dòng)到堆上,這個(gè)操作就是被我們稱作的“裝箱”。
裝箱(box)的詳細(xì)步驟:
在堆上分配一個(gè)內(nèi)存空間,大小等于需要裝箱的值類型對(duì)象的大小加上兩個(gè)引用類型對(duì)象都擁有的成員:類型對(duì)象指針和同步塊引用。
把堆棧上的值類型對(duì)象復(fù)制到堆上新分配的對(duì)象。
返回一個(gè)指向堆上新對(duì)象的引用,并且存儲(chǔ)到堆棧上被裝箱的那個(gè)值類型的對(duì)象里。
這個(gè)步驟不需要程序員自己編寫,在任何出現(xiàn)裝箱的地方,編譯器會(huì)自動(dòng)加上執(zhí)行以上功能的IL代碼。
所謂的拆箱就是裝箱對(duì)應(yīng)著的概念,但拆箱的過(guò)程和裝箱并不是倒過(guò)來(lái)就是:
拆箱(unbox.any)的詳細(xì)步驟
如果為待拆箱對(duì)象為null,拋出NullReferenceException異常。
如果引用指向的不是一個(gè)期望對(duì)象的已裝箱對(duì)象,拋出InvalidCastException異常。
獲取已裝箱對(duì)象中各個(gè)字段的地址,這個(gè)過(guò)程就是“拆箱”
需要說(shuō)明的是一般拆箱以后會(huì)伴隨著對(duì)象的拷貝,但拷貝操作已經(jīng)不是拆箱的范疇。
裝箱拆箱新能比較
了解了裝箱和拆箱的操作,我們可以清楚的明白:裝箱操作會(huì)導(dǎo)致數(shù)據(jù)在堆和棧上進(jìn)行拷貝,頻繁的裝箱操作會(huì)性能損失。而相比而言拆箱過(guò)程對(duì)性能損耗還是比較小的。
3 小結(jié)
裝箱和拆箱意味著堆和堆??臻g的一系列操作,毫無(wú)疑問(wèn),這些操作的性能代價(jià)是很大的,尤其是對(duì)于堆上空間的操作,速度相對(duì)于堆棧慢得多,并且可能引發(fā)垃圾回收,這些都將大規(guī)模的影響系統(tǒng)系能。
裝箱和拆箱操作經(jīng)常發(fā)生在以下連個(gè)場(chǎng)合:
值類型的格式化輸出
System.Object類型的容器
第一種情況,類型的格式化輸出往往伴隨一次裝箱操作,譬如:
using System;
namespace MaxValueTest
{
/// <summary>
/// DebugLZQ
/// http://www.cnblogs.com/DebugLZQ
/// </summary>
class Program
{
static void Main(string[] args)
{
int i = Int32.MaxValue;
Console.WriteLine("Int32的大值是"+i);//引發(fā)了一次不必要的裝箱操作
Console.WriteLine("Int32的大值是" + i.ToString());//ok
Console.ReadKey();
}
}
}
第二種情況更為常見(jiàn)一些,例如常用的容器ArrayList,就是一個(gè)典型的System.Object容器,任何值類型被放入到ArrayList的對(duì)象中,都會(huì)發(fā)生一次裝箱操作,而對(duì)應(yīng)的取出值類型對(duì)象會(huì)引發(fā)一次拆箱操作。
在.NET 2.0以后,引入了“泛型”的概念后,這些問(wèn)題得到了有效的解決。泛型允許定義針對(duì)某個(gè)特定類型(包括值類型)的容器,并且避免裝箱和拆箱。