[.NET] 什麼是 Boxing 和 Unboxing


[.NET] 什麼是 Boxing 和 Unboxing ?

介紹 裝箱 (Boxing) 和 拆箱 (Unboxing) 基本概念與使用上可能會有的誤解,並以 C# 範例來測試效能。。

Boxing

Boxing 將儲存 堆疊(stack)實值型別(Value type) 轉換為 object 類型,此轉換為 隱含轉換(implicit conversion)

  • Boxing 處理時,會在 堆積(heap) 產生新物件並將值複製過去。
  • 用”實值型別實”作的任何介面類型也適用。

類似你有一個商品在貨架上,把它裝箱起來並放置到某個倉庫,並紀錄(ref)它放置在哪個倉庫,方便後續查找。

int i = 123;

// Boxing
object o = i;

Boxing

透過之前介紹好用工具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;

Unboxing

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)

參考資料

Boxing 和 Unboxing

boxing 與 unboxing 重點整理與程式範例

Why do we need boxing and unboxing

Object type boxing with a reference type variable

Reference types


作者: PuTaoNi
版權聲明: 本站所有文章除特別聲明外,均採用 CC BY 4.0 許可協議。轉載請註明來源 PuTaoNi !
 上一篇
[.NET] 併發基本三原則原子性、可見性、有序性 [.NET] 併發基本三原則原子性、可見性、有序性
在設計併發(Concurrency)程式時往往忽略基本三原則原子性(Atomic)、可見性(Visibility)、有序性(Ordering),可能在程式執行時造成非預期的錯誤,透過下面介紹來瞭解這些原則因而避免錯誤產生。
2021-12-21
下一篇 
[工具] 解決記 choco 指令的煩惱 - Chocolatey GUI [工具] 解決記 choco 指令的煩惱 - Chocolatey GUI
Chocolatey GUI 是一個簡單的應用程式,可以讓你在 Windows 上安裝和管理 Chocolatey 所提供的套件。
2021-11-10
  目錄