裝箱和拆箱

原文:http://www.cnblogs.com/huashanlin/archive/2007/05/16/749359.html


幾個要點:

4:裝箱/拆箱是什麼?
裝箱:用於在垃圾回收堆中存儲值類型。裝箱是值類型到 object 類型或到此值類型所實現的任何接口類型的隱式轉換。
拆箱:從 object 類型到值類型或從接口類型到實現該接口的值類型的顯式轉換。 


5:爲何需要裝箱?(爲何要將值類型轉爲引用類型?)
一種最普通的場景是,調用一個含類型爲Object的參數的方法,該Object可支持任意爲型,以便通用。當你需要將一個值類型(如Int32)傳入時,需要裝箱。
另一種用法是,一個非泛型的容器,同樣是爲了保證通用,而將元素類型定義爲Object。於是,要將值類型數據加入容器時,需要裝箱。 


是哇?


6:裝箱/拆箱的內部操作。 


裝箱:
對值類型在堆中分配一個對象實例,並將該值複製到新的對象中。按三步進行。
第一步:新分配託管堆內存(大小爲值類型實例大小加上一個方法表指針和一個SyncBlockIndex)。 


SyncBlockIndex: This index has multiple purposes: As implied by its name it is used for synchronization, whenever the "lock" keyword is used. It is also used as the default hash code for Object.GetHashCode(). It does not provide the best distribution properties for a hash code, but it meets the minimum requirements for a hash code for reference equality. The syncindex remains constant throughout the life of the object.


第二步:將值類型的實例字段拷貝到新分配的內存中。
第三步:返回託管堆中新分配對象的地址。這個地址就是一個指向對象的引用了。
有人這樣理解:如果將Int32裝箱,返回的地址,指向的就是一個Int32。我認爲也不是不能這樣理解,但這確實又有問題,一來它不全面,二來指向Int32並沒說出它的實質(在託管堆中)。 



拆箱:
檢查對象實例,確保它是給定值類型的一個裝箱值。將該值從實例複製到值類型變量中。
有書上講,拆箱只是獲取引用對象中指向值類型部分的指針,而內容拷貝則是賦值語句之觸發。我覺得這並不要緊。最關鍵的是檢查對象實例的本質,拆箱和裝箱的類型必需匹配,這一點上,在IL層上,看不出原理何在,我的猜測,或許是調用了類似GetType之類的方法來取出類型進行匹配(因爲需要嚴格匹配)。 


7:裝箱/拆箱對執行效率的影響
顯然,從原理上可以看出,裝箱時,生成的是全新的引用對象,這會有時間損耗,也就是造成效率降低。
那該如何做呢?
首先,應該儘量避免裝箱
比如上例2的兩種情況,都可以避免,在第一種情況下,可以通過重載函數來避免。第二種情況,則可以通過泛型來避免。
當然,凡事並不能絕對,假設你想改造的代碼爲第三方程序集,你無法更改,那你只能是裝箱了。 


對於裝箱/拆箱代碼的優化,由於C#中對裝箱和拆箱都是隱式的,所以,根本的方法是對代碼進行分析,而分析最直接的方式是瞭解原理結何查看反編譯的IL代碼。比如:在循環體中可能存在多餘的裝箱,你可以簡單採用提前裝箱方式進行優化。 


8:對裝箱/拆箱更進一步的瞭解
裝箱/拆箱並不如上面所講那麼簡單明瞭,比如:裝箱時,變爲引用對象,會多出一個方法表指針,這會有何用處呢?
我們可以通過示例來進一步探討。
舉個例子。
Struct A : ICloneable  ?? struct怎麼能繼承啊? 操,真的可以繼承

    public Int32 x; 
    public override String ToString() { 
    return String.Format(”{0}”,x); 

    public object Clone() { return MemberwiseClone(); }  只要實現接口規定的方法就可以

static void main() 

    A a; 
    a.x = 100; 
    Console.WriteLine(a.ToString()); 
    Console.WriteLine(a.GetType()); 
    A a2 = (A)a.Clone(); 
    ICloneable c = a2; 
    Ojbect o = c.Clone(); 

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()。無需裝箱,在託管堆中對上一步已裝箱的對象進行調用。
附:其實上面的基於一個根本的原理,因爲未裝箱的值類型沒有方法表指針,所以,不能通過值類型來調用其上繼承的虛方法。另外,接口類型是一個引用類型。對此,我的理解,該方法表指針類似C++的虛函數表指針,它是用來實現引用對象的多態機制的重要依據。 


。。。。。。。。。。中間幾點,過幾天再看。。。。


11、-------------------------
NET的所有類型都是由基類System.Object繼承過來的,包括最常用的基礎類型:int, byte, short,bool等等,就是說所有的事物都是對象。如果申明這些類型得時候都在堆(HEAP)中分配內存,會造成極低的效率!(箇中原因以及關於堆和棧得區別會在另一篇裏單獨得說說!)
.NET如何解決這個問題得了?正是通過將類型分成值型(value)和引用型(regerencetype),C#中定義的值類型包括原類型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚舉(enum)、結構(struct),引用類型包括:類、數組、接口、委託、字符串等
值型就是在棧中分配內存,在申明的同時就初始化,以確保數據不爲NULL;
引用型是在堆中分配內存,初始化爲null,引用型是需要GARBAGE COLLECTION來回收內存的,值型不用,超出了作用範圍,系統就會自動釋放!
下面就來說裝箱和拆箱的定義!
裝箱就是隱式的將一個值型轉換爲引用型對象。比如:
int i=0;
Syste.Object obj=i;

這個過程就是裝箱!就是將i裝箱!
拆箱就是將一個引用型對象轉換成任意值型!比如:
int i=0;
System.Object obj=i;
int j=(int)obj;

這個過程前2句是將i裝箱,後一句是將obj拆箱!

發佈了77 篇原創文章 · 獲贊 1 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章