這一篇文章聊一下 Int32.ToString() 在 .NET Core 3.0 的小小變更。
過去在說明 C# 字串池效果的時候,我常常會寫一個這樣的範例說明在非使用字串常值的狀況下會在 Heap 產生不同的 string 型別物件:
class Program
{
static void Main(string[] args)
{
int i = 1;
string s1 = i.ToString();
string s2 = i.ToString();
Console.WriteLine(object.ReferenceEquals(s1, s2));
Console.ReadLine();
}
}
這樣的範例在 .Net Framwrork 4.8 (含) 和 .NET Core 2.2(含) 以前都是符合預期且運作愉快,執行的結果是 false;但是在 .NET Core 3.0/3.1 的結果卻是 true。 這引起了我的好奇心,於是我稍微修改了賦予 i 變數的值:
class Program
{
static void Main(string[] args)
{
int i = 99;
string s1 = i.ToString();
string s2 = i.ToString();
Console.WriteLine(object.ReferenceEquals(s1, s2));
Console.ReadLine();
}
}
這個執行結果在 .NET Core 3.0/3.1 狀況下是 false。這下我更好奇了,i = 1 和 i = 99 居然會有相反的結果,為什麼?讓我們繼續看下去。
為了得到解答,我追了一下 Int32.ToString() method 的原始碼,這個方法的內容如下:
public override string ToString()
{
return Number.FormatInt32(m_value, null, null);
}
繼續往內挖掘 Number.FormatInt32 method,在一個名為 UInt32ToDecStr(uint value, int digits) 的 internal method 中做了一個這樣的處理:
int bufferLength = Math.Max(digits, FormattingHelpers.CountDigits(value));
if (bufferLength == 1)
{
return s_singleDigitStringCache[value];
}
簡單說就是當UInt32 數值轉出的字串的長度為 1 的時候,則直接返回內部 s_singleDigitStringCache 中的值,s_singleDigitStringCache 是 Nunmer class 中的一個靜態私有唯讀欄位 (private static readonly filed) ,其型別為 string[] (字串陣列)。這個陣列的內容很簡單:
private static readonly string[] s_singleDigitStringCache = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
這解釋了為什麼我用 i = 1 和 i = 99 會導致不同的結果,因為當賦予 i 的值為 0 ~ 9的時候,會直接從 s_singleDigitStringCache 取得對應的字串返回,也就是它永遠會返回同一個 string instance,使得執行結果為 true;但當 i > 9 或 i < 0 則沒有對應的機制,因此會產生不同的 string instance。
很微小的問題,但也許哪一天會被坑,隨手記錄一下。