C#基礎:簡述裝箱和拆箱原理


三、測試

我們知道,值類型的變量是在堆棧上分配內存的,而引用類型包括System.Object的對象是在堆上分配內存的,基於這一特點,當值類型被類型轉換時,會在堆棧和堆上進行一系列的操作,這就是裝箱和拆箱的來源。充分理解裝箱和拆箱,有助於程序員編寫高效率的代碼。

1、裝箱和拆箱的基本概念

我們知道,所有的值類型都繼承自System.ValueType,而System.ValueType繼承自System.Object。所有的值類型對象都分配在堆棧上,而所有的引用類型包括System.Object對象都分配在堆上。問題隨之而來,既然System.Object是所有值類型的基類,那所有的值類型必然都可以隱式的轉換成System.Object類型,此時這個對象會被放在哪里呢,堆棧上面還是堆上面?實際上,當這個轉換發生時,CLR需要做額外的工作把堆棧上的值類型移動到堆上,這個操作就被稱為裝箱。來看一個裝箱所需要的詳細步驟。

  1. 在堆上分配一個內存空間,大小等於需要裝箱的值類型對象的大小加上兩個引用類型對象都擁有的成員:類型對象指針和同步塊引用。
  2. 把堆棧上的值類型對象復制到堆上新分配的對象。
  3. 返回一個指向堆上新對象的引用,並且存儲到堆棧上被裝箱的那個值類型的對象里。

這些步驟都不需要程序員自己編寫,在任何出現裝箱的地方,編譯器會自動地加上執行以上功能的中間代碼。下圖展示了裝箱前后堆和堆棧的變化。

理解了裝箱之后,就可以很方便地理解拆箱操作了。所謂的拆箱,就是裝箱操作的反操作,把堆中的對象復制到堆棧中,並且返回其值。需要注意的是,拆箱操作將判斷被拆箱的對象類型和將要被復制的值類型引用是否一致,如果不一致,將會拋出一個InvalidCastException的異常。這里的類型匹配並不采用任何顯示的類型轉換。下面的代碼展示了這一特性。

static void Main(string[] args)
{
    try
    {
        Int32 i = 3;
        // 裝箱
        Object o = i;
        // 拆箱,類型轉換失敗
        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();
}

程序運行結果:

分析上面的代碼,在第一組裝箱拆箱操作中,代碼試圖把一個原來類型為Int32的值類型裝箱后的對象拆箱成Int16的變量,這樣的拆箱是非法的,運行時會拋出一個InvalidCastException的異常。而在第二組裝箱拆箱操作中,就進行了正確的類型匹配,拆箱順利完成。

2、裝箱和拆箱對性能的影響,以及如何避免裝箱拆箱

裝箱和拆箱都意味着堆和堆棧空間的一系列操作,毫無疑問,這些操作的性能代價是很大的,尤其對於堆上空間的操作,速度相對於堆棧的操作慢的多,並且可能引發垃圾回收,這些都將大規模地影響系統的性能。如何避免裝箱拆箱操作,是程序員在編寫代碼時需要時刻考慮的一個問題。裝箱和拆箱操作常發生在以下兩個場合:

  1. 值類型的格式化輸出。
  2. System.Object類型的容器。

第一種情況,值類型的格式化輸出往往會涉及一次裝箱操作。例如下面的兩行代碼:

int i = 10;
Console.WriteLine("i的值是:" + i);

代碼完全能夠通過編譯並且正確執行,但卻引發了一次不必要的裝箱操作。在第2行代碼上,值類型i被作為一個System.Object對象傳入方法之中,這樣的操作完全可以通過下面的改動來避免:

int i = 10;
Console.WriteLine("i的值是:" + i.ToString());

改動后的代碼調用了i的ToString()方法來得到一個字符串對象。由於字符串是引用類型,所以改動后的代碼就不在涉及裝箱操作。

第二種情況更為常見一些。例如常用的容器類ArrayList,就是一個典型的System.Object容器。任何值類型被放入ArrayList的對象中,都會引發一次裝箱操作。而對應的,取出值類型對象就會引發一次拆箱操作。在.NET 1.1之前,這樣的操作很難避免,但在.NET 2.0推出了泛型的概念后,這些問題得到了有效的解決。泛型允許定義針對某個特定類型(包括值類型)的容器,並且有效的避免裝箱和拆箱。

三、總結

裝箱和拆箱本質上是值類型在轉換到System.Object時引發的堆棧和堆的一系列移動操作。裝箱時值類型從堆棧上被復制到堆上,而拆箱時從堆上復制到堆棧上。裝箱和拆箱對性能有比較大的影響,應該避免任何沒有必要的裝箱和拆箱操作。

在可以確定類型的情況下應該使用泛型技術而避免使用針對System.Object類型的容器,這樣可以有效避免大規模地使用裝箱和拆箱操作。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM