string s1 = "ABCD1234";
string s2 = "ABCD1234";
Console.WriteLine(object.ReferenceEquals(s1, s2)); // True ✅
string s3 = "ABCD";
string s4 = "1234";
string s5 = "ABCD" + "1234"; // 編譯期常數摺疊
string s6 = "ABCD" + s4; // 執行期拼接
string s7 = s3 + s4; // 執行期拼接
Console.WriteLine(object.ReferenceEquals(s1, s5)); // True ✅
Console.WriteLine(object.ReferenceEquals(s1, s6)); // False ❌
Console.WriteLine(object.ReferenceEquals(s1, s7)); // False ❌
// 手動駐留
Console.WriteLine(object.ReferenceEquals(s1, string.Intern(s6))); // True
Console.WriteLine(object.ReferenceEquals(s1, string.Intern(s7))); // True
.NET 維護一張全域哈希表 Intern Pool (字串駐留池),Key 為字串內容,Value 為該字串在托管堆上的唯一引用。
string.Intern(str):若池中已有相同內容,返回池中引用;否則將 str 加入池並返回其引用。string.IsInterned(str):檢查池中是否存在,存在返回引用,否則返回 null (不加入)。| 程式碼 | 類型 | 是否自動駐留 | 說明 |
|---|---|---|---|
"ABCD1234" |
字串常值 | ✅ 是 | 編譯期寫入模組中繼資料 #US heap,模組載入時自動進 Intern Pool |
const string C = "ABC"; |
常數 | ✅ 是 | 同字串常值 |
"AB" + "CD" |
常數摺疊 | ✅ 是 | 編譯器計算結果 "ABCD" 視為常值 |
s3 + s4 (變數拼接) |
執行期運算 | ❌ 否 | 每次執行產生新物件,除非 JIT 極罕見優化 |
string.Concat(...) |
方法調用 | ❌ 否 | 同執行期拼接 |
new StringBuilder().ToString() |
建構器 | ❌ 否 | 新物件 |
string.Intern(s) |
手動駐留 | ✅ 手動 | 強制進池/獲取池中引用 |
ReferenceEquals / == (參考比較) 極快,可作為 Equals (內容比較) 的快速路徑。Intern Pool 隨 AppDomain 存亡 (進程結束才釋放)。大量動態字串呼叫 Intern() 會導致**記憶體洩漏** (池膨脹不可回收)。string.Intern 自身有鎖競爭開銷,高併發下成為瓶頸。string.Equals(a, b, StringComparison.Ordinal) 或 a == b (已重載為內容比較),JIT 會優化先比引用再比內容,無需手動干預。Intern 節省記憶體,但集合必須固定且有限。ConcurrentDictionary<string, string> 自行管理,可控生命週期與淘汰策略。string.IsInterned / Intern 實作優化,鎖粒度更細。StringBuilder.GetChunks 等避免中間字串分配,減少動態字串產生。字串駐留是 .NET 執行期為**編譯期常數**提供的記憶體去重與比較優化機制。**執行期動態字串預設不駐留**。理解這一機制能解釋 ReferenceEquals 的反直覺行為,但**切勿濫用 string.Intern() 處理動態資料**,應依賴 JIT 對 ==/Equals 的雙重檢查優化,或自行管理弱引用快取。