[.NET] 什麼是 Boxing 和 Unboxing ?
介紹 裝箱 (Boxing) 和 拆箱 (Unboxing) 基本概念與使用上可能會有的誤解,並以 C# 範例來測試效能。。
Boxing
Boxing 將儲存 堆疊(stack) 的 實值型別(Value type) 轉換為 object 類型,此轉換為 隱含轉換(implicit conversion) 。
- Boxing 處理時,會在 堆積(heap) 產生新物件並將值複製過去。
- 用”實值型別實”作的任何介面類型也適用。
類似你有一個商品在貨架上,把它裝箱起來並放置到某個倉庫,並紀錄(ref)它放置在哪個倉庫,方便後續查找。
int i = 123;
// Boxing
object o = i;
透過之前介紹好用工具sharplab反編譯出來的 IL,如果想知道更詳細功能可以參考此篇。
IL_0000: nop
IL_0001: ldc.i4.s 123
IL_0003: stloc.0
IL_0004: ldloc.0
IL_0005: box [System.Private.CoreLib]System.Int32 // Boxing
IL_000a: stloc.1
IL_000b: ret
Unboxing
Unboxing 就是 Boxing 的相反,將 object 類型明確轉換為實值型別。
- 注意 object 是否為 Null 如果是會造成
NullReferenceException
- 嘗試不相容的實值類型進行 Unboxing 會造成
InvalidCastException
(所以要先檢查是否相容)
也同等於,將倉庫內的商品取出並放置到貨架上。
object o = 123;
// Unboxing
int i = (int)o;
IL_0000: nop
IL_0001: ldc.i4.s 123
IL_0003: box [System.Private.CoreLib]System.Int32
IL_0008: stloc.0
IL_0009: ldloc.0
IL_000a: unbox.any [System.Private.CoreLib]System.Int32 // Unboxing
IL_000f: stloc.1
IL_0010: ret
Boxing 和 Unboxing 效能測試
private static void WithoutBox(object obj)
{
int i = 123;
}
private static void Boxing(object obj)
{
object i = 123;
}
private static void UnBoxing(object obj)
{
int i = (int)obj;
}
# WithoutBox
IL_0000: nop
IL_0001: ldc.i4.s 123
IL_0003: stloc.0
IL_0004: ret
# Boxing
IL_0000: nop
IL_0001: ldc.i4.s 123
IL_0003: box [System.Private.CoreLib]System.Int32 // Boxing
IL_0008: stloc.0
IL_0009: ret
# UnBoxing
IL_0000: nop
IL_0001: ldarg.0
IL_0002: unbox.any [System.Private.CoreLib]System.Int32 // Unboxing
IL_0007: stloc.0
IL_0008: ret
測試 100,000,00 次後可以發現在處理 Boxing 的時候,最為耗時幾乎是沒有任何處理快兩倍的時間,UnBoxing 也是需要消耗間去處理,所以在寫代碼時盡量避免 Boxing 或 UnBoxing 產生。
WithoutBox Test 耗時 381 毫秒
Boxing Test 耗時 728 毫秒
UnBoxing Test 耗時 455 毫秒
這裡是測試範例,如有理解錯誤歡迎大家多多指教,謝謝
Boxing 範例
範例 1:不小心就 Boxing 了
通常我們在寫 Log 時候,很常在寫 Log 訊息使用字串簡化寫法,但其實不小心就造成 Boxing 產生,此種狀況可能造成效率問題。
var ex1 = 123;
Console.WriteLine($" {ex1}"); // Boxing
Console.WriteLine($" {ex1.ToString()}");
IL_000a: box [System.Private.CoreLib]System.Int32 // Boxing
IL_000f: call string [System.Private.CoreLib]System.String::Format(string, object)
IL_0014: call void [System.Console]System.Console::WriteLine(string)
IL_0021: call instance string [System.Private.CoreLib]System.Int32::ToString()
IL_0026: call string [System.Private.CoreLib]System.String::Concat(string, string)
IL_002b: call void [System.Console]System.Console::WriteLine(string)
範例 2:以為有 Boxing 產生
重點:Boxing 只有在執行 實值型別(Value type) , string 是 參考型別所以不會產生 Boxing。 (參考)
var ex2 = "123";
object obj2 = ex2;
obj2 = "456";
Console.WriteLine($"Example2 string result: {ex2}");
Console.WriteLine($"Example2 object result: {obj2}");
IL_000f: ldstr "Example2 string result: "
IL_0014: ldloc.0
IL_0015: call string [System.Private.CoreLib]System.String::Concat(string, string)
IL_001a: call void [System.Console]System.Console::WriteLine(string)
IL_0020: ldstr "Example2 object result: {0}"
IL_0025: ldloc.1
IL_0026: call string [System.Private.CoreLib]System.String::Format(string, object)
IL_002b: call void [System.Console]System.Console::WriteLine(string)
參考資料
Why do we need boxing and unboxing