C#

C# Boxing and Unboxing

Posted by Max Lin on 2021-08-03

前言

你是否曾疑惑:

var num = 3345678;
var result = $"我的電話: {num}";

為何這一段程式碼可以把 int 型別直接插進字串呢?

事實上

可以把 $"" 看成 String.Format 的語法糖,在這種情況 String.Format 底層其實做了 Boxing 的動作,使 int 最終會轉為 string 後再填入字串裡,這樣做其實無形中產生了一些效能成本。

什麼是 Boxing & Unboxing

Boxing & Unboxing 前,要先了解 實值型別參考型別 的存儲方式。
可以參考我的另一篇 – C# 實值型別 & 參考型別

Boxing & Unboxing:

Boxing 是指 實值型別 轉為 參考型別 中間的轉換過程,反之為 Unboxing,也就是 參考型別 轉為 實值型別 的轉換過程。

※ 若兩者都為 實值型別參考型別,就不會發生此情況。

說明如下:

int i = 123;
object o = i; // boxing ( 實質 => 參考 )
int j = (int)o // unboxing ( 參考 => 實質 )

在轉換過程中,實值型別的值原本存於 Stack 記憶體裡,但經轉換成參考型別 object ,會將值轉存於 heap 記憶體,反之亦然,這樣一來一往無形中就會產生效能損耗。

String.Format

再回頭看 String.Format 這個函式,其中一個多載為:
public static string Format (string format, params object?[] args)
可以看到傳入參數是 object 型別,也就代表不管傳入參數的資料型態為何,最終都將被強轉成 object, 所以,當傳入 實值型別 參數時,就會發生 Boxing

測試如下:

void Main()
{
	// arrange
	var sw = new Stopwatch();
	var count = 10000000;	
	
	// 裝箱測試
	sw.Start();	
	for (int i = 0; i < count; i++)
	{
		var result = String.Format("{0}{1}{2}", i, i, i);		
	}	
	sw.Stop();
	
	Console.WriteLine($"Boxing Test: {sw.ElapsedMilliseconds} ms");
		
	// 不裝箱測試
	sw.Restart();
	for (int i = 0; i < count; i++)
	{
		var result = String.Format("{0}{1}{2}", i.ToString(), i.ToString(), i.ToString());
	}
	sw.Stop();
	
	Console.WriteLine($"No Boxing Test: {sw.ElapsedMilliseconds} ms");
    
    // Output
    // Boxing Test: 4919 ms
    // No Boxing Test: 4673 ms
}

參考

[ASP.NET] Boxing(裝箱) 與 UnBoxing(拆箱) 測試
那些年String.Format中的Boxing和UnBoxing