Skip to main content

[探索 5 分鐘] boxing 與 unboxing 重點整理與程式範例



博大精深的設計 ! MSDN 官方文件寫得很清楚, 這兩個詞兒都算動詞, 是把型別與 System.Object 超基底型別互相轉型的過程, 但不知道各位有沒有跟我有相同的困擾, 以 int 與 object 兩個型別為例, 到底 int to object是 boxing, 還是 object to int 是 boxing ? 用猜的怎麼猜怎麼錯。

這邊提供一個小小記憶法, 就是..., 圖像記憶法 !
總之裝箱的過程, 就是 boxing, 就是放到一個隱含的型別中: System.Object, 那你是不是就看不到了呢 ? 你應該就看不到了, 因為他就是被一個 box 裝起來了, 你看不到他, 你看不到他, 你看不到他。

中文翻譯如其名 boxing 就是裝箱, unboxing 就是拆箱; 然而他的用途或特色呢 ? 網路上有很多資料, 喜歡哪一種講法就看個人, 我的歸納重點。
  • 易於指標操作 
  • 多型的操作彈性
  • 注意副效應 (性能消耗與轉型錯誤)

易於指標操作

來看看這個很像 C++ 的指標操作代碼。
long num1; 
long num2 = num1; 
long* ptr1 = &num1; //address-of operator & 
這個例子剛好是 long, 但他可以是任意型別。num1 是長整數占用 8 bytes 記憶體; 而 ptr1 是個指標變數占用 4 bytes 記憶體。也就是說, 不管資料型別為何或 size 多大, 他被 assign 給某個變數 (num2) 或指標變數 (ptr1) 後, 這個變數本身所需要占據的記憶體就剩 4 bytes 的指標位址 (而資料儲存的地方是另外一回事), 後續拷貝變數, 傳遞變數, 成本都低的非常多 (想像當資料 size 很大時)。是不是稍微可以了解為何會有 reference type 了呢 ? 如 string, object。

多型的操作彈性

boxing 是轉為 System.Object 型別的物件, 是所有類別的母類別, 在進行資料結構設計就出現一種多型的技巧, 如 List<object> 的 Item 與 Dictionary<string, object> 的 Item.Value, 可以吃下各類型的實體; 當然, 也可以是 List<BaseClass>, Dictionary<string, BaseClass>, 不需要非常清楚知道子類別 runtime 型別, 只需要繼承某個母類別, 就可以針對這個物件進行轉型或統一的行為操作了, 是一種抽象設計。當然, 這裡的範例指的是編程語言如 C++, Java, C#; 至於 Python, php,  JavaScript 等腳本語言本身就支持類似 a = [ '1', 2, 3.0, [] ] 這種操作。

注意副效應 (性能消耗與轉型錯誤)

型別轉換 就是常聽到的 casting 或是 type conversion, 是要消耗 cpu 資源的, 運氣好一點是編譯器會告知你 A 不能轉 B, 運氣差就是 runtime 錯誤了, 所以千萬別太任性的轉換, 應要有適量的抽象化設計, 清楚的註解, 否則當你看到一堆 object 或 dynamic 型別的物件, 接手的人應該會譙翻天, 尤其還耗效能。

用數據來佐證好了, 寫三個很簡單的 function, 各寫一行 assign 行為 : 1). 無 casting, 2). casting 為 object, 3). casting 為 long, 跑 1000 萬次來測試性能上的差異。
public static void WithoutBox(long s)
{
    long l1 = s;
}

public static void Boxing(long s)
{
    object o = s;
}

public static void UnBoxing(object s)
{
    long l2 = (long)s;
}
是不是發現, 只是一個 long 型別轉來轉去, 不轉換的 WithougBox() 與其他要轉換的性能快 1 倍以上, 而 unboxing 又比 boxing 快。尤其 unboxing 還會有 System.InvalidCastException 的錯誤機率, 比效能危害更大。

程式筆記

如果有人喜歡用程式碼來記憶, 也沒關係, 擷取 MSDN 官網的 程式碼 片段, 短短三行就行了。
int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;   // unboxing

參考資料

  • https://msdn.microsoft.com/zh-tw/library/system.object(v=vs.110).aspx
  • https://msdn.microsoft.com/en-us/library/system.invalidcastexception(v=vs.110).aspx
  • https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/types/boxing-and-unboxing
  • https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/unsafe-code-pointers/how-to-obtain-the-address-of-a-variable
  • https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/types/casting-and-type-conversions
  • https://stackoverflow.com/questions/2111857/why-do-we-need-boxing-and-unboxing-in-c

Comments