Effective C# 04.以內插字串取代 string.Format Effective C# 04.以內插字串取代 string.Format (Replace string.Format() with Interpolated Strings)

Published on Wednesday, September 25, 2024

在 C# 6 引入新的字串內插(string interpolation)的寫法,相比傳統的字串格式化寫法的可讀性還高。

使用 string.Format 需要先設定 formatString,最後在帶入多個 params 到 formatString 裡面來完成字串格式化。

int value1 = 16932;
int value2 = 15421;
string formatString = "{0:D} And {1:D} = {2:D}";
string result = String.Format(formatString, value1, value2, value1 & value2);
Console.WriteLine(result);

使用 interpolation 不需要設定 formatString,只需要在引號前面加上 $ 編譯器就會知道這是個內插字串。

int value1 = 16932;
int value2 = 15421;
string interpolateString = $"{value1:D} And {value2:D} = {value1 & value2:D}" ;
Console.WriteLine(interpolateString);

從上面兩個例子可以發覺 string.Format 有一個最大的缺點就是參數留的空位與實際參數位置是分開的,假如參數多達十個以上會很難對應到空位裡, 或者會算錯實際參數的位置。

內插字串修改了這個最大的缺點,讓實際的空位跟參數可以寫在一起,可讀性提高非常多。

Boxing

需要注意在 C# 10 以前的寫法可以得知 interpolation 背後還是會呼叫 String.Format, 另外 Math.PI 會將回傳的 double 類型裝箱成 object 類型,這個操作會對效能產生影響。

Console.WriteLine($"The value of pi is {Math.PI}");

IL_000F 步驟為 boxing;

IL_0000	nop	
IL_0001	ldstr	"The value of pi is {0}"
IL_0006	ldc.r8	18 2D 44 54 FB 21 09 40  // 3.141592653589793
IL_000F	box	Double
IL_0014	call	String.Format(String, Object)
IL_0019	call	Console.WriteLine(String)
IL_001E	nop	
IL_001F	ret	

要避免 boxing 可以使用以下這個寫法直接將 double 轉成字串。

Console.WriteLine($"The value of pi is {Math.PI.ToString()}");

從 IL 碼可以得知沒有 box 呼叫,並且背後是使用 String.Concat 來將兩個字串合併起來。

IL_0000	nop	
IL_0001	ldstr	"The value of pi is "
IL_0006	ldc.r8	18 2D 44 54 FB 21 09 40  // 3.141592653589793
IL_000F	stloc.0	
IL_0010	ldloca.s	00 
IL_0012	call	Double.ToString()
IL_0017	call	String.Concat(String, String)
IL_001C	call	Console.WriteLine(String)
IL_0021	nop	
IL_0022	ret

根據 pull request 內容在 C# 10 和之後的版本添加了 InterpolatedStringHandler 修改了原本背後呼叫 String.Format 的行為,同時避免發生 boxing 的行為。

IL_0000	nop	
IL_0001	ldloca.s	00 
IL_0003	ldc.i4.s	13  // 19
IL_0005	ldc.i4.1	
IL_0006	call	DefaultInterpolatedStringHandler..ctor
IL_000B	ldloca.s	00 
IL_000D	ldstr	"The value of pi is "
IL_0012	call	DefaultInterpolatedStringHandler.AppendLiteral(String)
IL_0017	nop	
IL_0018	ldloca.s	00 
IL_001A	ldc.r8	18 2D 44 54 FB 21 09 40  // 3.141592653589793
IL_0023	call	DefaultInterpolatedStringHandler.AppendFormatted<Double>(Double)
IL_0028	nop	
IL_0029	ldloca.s	00 
IL_002B	call	DefaultInterpolatedStringHandler.ToStringAndClear()
IL_0030	call	Console.WriteLine(String)
IL_0035	nop	
IL_0036	ret

Summary

使用 String InterpolationStringBuilder 取代 string.Format 可以獲得更好的效能。 如果很在意效能建議是直接手動呼叫 ToString() 將值型別轉成參考型別,但是新版的寫法已經避免了 boxing 所以兩種寫法相差不多。 另外要注意不要直接用字串內插來寫 SQL 語法,因為會導致 SQL Injection 的問題。