裝箱與拆箱是.Net裡很重要的一個概念,可將值類型視為參考類型一般使用,因此我們在程式撰寫時,可以將值類型以System.Object型態包裝,並保存於Managed 堆積中,開發人員不需自行處理這部份的轉換,這樣的動作在.Net程式中會自動發生。雖然這貼心的小動作會讓程式的撰寫變得很方便,但卻讓裝箱與拆箱動作的發生更不容易被差覺,更糟的是裝箱與拆箱的動作會產生額外不必要的性能耗費。要避免裝箱與拆箱所產生的性能耗費最重要的是我們必需了解到這兩個動作的用途與其發生的時機,能掌握到發生的時機就可以進一步的使用一些技巧來避開。

裝箱…簡單的來說是用來將值類型轉換為參考類型的動作。該動作在運作時會在heap上配置一份記憶體空間,在該塊記憶體空間建立匿名參考物件,並將本來存於stack的值類型物件的值賦予給該匿名參考物件,因此能讓值類型應用於只能使用參考類型的場合。

[C#]Effective C# Item 17: Minimize Boxing and Unboxing

裝箱動作會於將值類型物件指派給參考類型變數時自動發生,像是:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace ConsoleApplication18
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person() { Name="Larry"};

            object o1 = (object)p;  // boxing
            object o2 = p;  // Implicit boxing
            ArrayList array = new ArrayList();
            array.Add(p); //Implicit boxing

            //Unboxing
            Person p1 = (Person)o1;
            Person p2 = (Person)o2;

            p1.Name = "Larry1";
            p2.Name = "Larry2";

            Person p3 = (Person)array[0];
            p3.Name = "Larry3";

            Console.WriteLine(string.Format("p = {0}", p));
            Console.WriteLine(string.Format("p1 = {0}", p1));
            Console.WriteLine(string.Format("p2 = {0}", p2));
            Console.WriteLine(string.Format("p3 = {0}", p3));
            Console.WriteLine(string.Format("array[0] = {0}", array[0]));
        }

        public struct Person
        {
            public string Name { get; set; }

            public override string ToString()
            {
                return Name;
            }
        }
    }
}

運行後可以發現運作時當把值類型轉換為參考類型時會在背後自行產生對應的複本,這就是裝箱。

[C#]Effective C# Item 17: Minimize Boxing and Unboxing

若要避免裝箱動作的發生,需避免值類型隱含轉換到System.Object,可考慮使用泛型避免使用System.Object當作參數類型。

至於拆箱,則是跟裝箱成對的動作,可從已裝箱的物件中將原本值類型的副本提取出來。

[C#]Effective C# Item 17: Minimize Boxing and Unboxing

當企圖從參考類型中提取出值類型時,拆箱的動作就會自動發生。像是上述程式裡的:

//Unboxing
Person p1 = (Person)o1;
Person p2 = (Person)o2;

若要避免拆箱動作的發生,可盡可能的採用System.Array、泛型、或是介面編程的方式來避免。像是:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace ConsoleApplication18
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person() { Name="Larry"};

            ArrayList array1 = new ArrayList();
            array1.Add(p); //Implicit boxing

            Person[] array2 = new Person[] { p };

            ((IPersonName)array1[0]).Name = "Larry1";
            array2[0].Name = "Larry2";

            Console.WriteLine(string.Format("p = {0}", p));
            Console.WriteLine(string.Format("array1[0] = {0}", array1[0]));
            Console.WriteLine(string.Format("array2[0] = {0}", array2[0]));
        }

        public interface IPersonName
        {
            string Name { get; set; }
        }

        public struct Person:IPersonName
        {
            public string Name { get; set; }

            public override string ToString()
            {
                return Name;
            }
        }
    }
}

運行結果如下:

[C#]Effective C# Item 17: Minimize Boxing and Unboxing

  • Boxing 和 Unboxing (C# 程式設計手冊)
  • Boxing 轉換 (C# 程式設計手冊)
  • Unboxing 轉換 (C# 程式設計手冊)