字串 是 C# 中使用最頻繁的型別之一。string(System.String 別名)是不可變的參考型別,採用 UTF-16 編碼。理解字串特性、掌握核心 API、避免效能陷阱,是寫出高品質 C# 程式的基本功。
// 1. 字串常值
string s1 = "Hello World";
string s2 = @"C:\Path\To\File"; // 逐字字串:不處理逸出字元,可跨行
// 2. 建構函式
char[] chars = { 'H', 'e', 'l', 'l', 'o' };
string s3 = new string(chars);
string s4 = new string('*', 10); // "**********"
// 3. 串連
string s5 = "Hello" + " " + "World";
string s6 = string.Concat("Hello", " ", "World");
// 4. 插值字串 (C# 6+)
var name = "Alice"; var age = 30;
string s7 = $"Name: {name}, Age: {age}";
// 5. 格式化
string s8 = string.Format("{0:C}", 1234.56); // "$1,234.56"
string s9 = string.Format("{0:yyyy-MM-dd}", DateTime.Now);
// 6. StringBuilder (高效能建立)
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.Append(i).Append(',');
string s10 = sb.ToString();
字串不可變:任何「修改」操作都會建立新字串實例。
// 糟糕:迴圈中串連,產生 1000 個中間字串,GC 壓力大
string bad = "";
for (int i = 0; i < 1000; i++) bad += i.ToString();
// 正確:StringBuilder
var sb = new StringBuilder(1000 * 4); // 預估容量
for (int i = 0; i < 1000; i++) sb.Append(i);
string good = sb.ToString();
經驗法則:固定次數串連用 + / 插值;迴圈/不定次數用 StringBuilder。
| 分類 | 成員 | 說明 |
|---|---|---|
| 屬性 | Length | 字元數 (UTF-16 代碼單元數) |
Chars[int] | 索引器存取單一字元 (唯讀) | |
| 搜尋 | IndexOf/LastIndexOf | 尋找字元/子字串位置,支援起始索引、比較選項 |
Contains | 是否包含子字串 (C# 7.0+ 支援 StringComparison) | |
StartsWith/EndsWith | 前綴/後綴比對 | |
IndexOfAny | 尋找任一字元位置 | |
| 擷取 | Substring | 擷取子字串 (起始、長度) |
Split | 分割字串陣列,支援移除空白項、限制數量 | |
Remove | 移除指定範圍字元 | |
Trim/TrimStart/TrimEnd | 移除前導/尾隨空白或指定字元 | |
| 修改(回傳新字串) | Replace | 取代字元/子字串 |
Insert | 插入子字串 | |
ToUpper/ToLower | 大小寫轉換 (文化特性敏感) | |
PadLeft/PadRight | 填充字元至指定長度 | |
| 比較 | Equals | 相等比較,支援 StringComparison |
Compare | 排序比較,支援文化特性、忽略大小寫 | |
CompareTo | 實作 IComparable | |
| 轉換 | ToCharArray | 轉字元陣列 |
Join | 串連陣列元素 (靜態方法) | |
Format | 複合格式化 (靜態方法) |
關鍵建議:明確指定 StringComparison,避免預設文化特性導致 Bug:
// 檔案路徑、XML 標籤、內部識別碼:用序數比較 (快、確定)
if (path.EndsWith(".config", StringComparison.OrdinalIgnoreCase)) { }
// 使用者輸入、顯示用排序:用目前文化特性
if (userInput.Equals("admin", StringComparison.CurrentCultureIgnoreCase)) { }
// 內部邏輯、不區分文化特性:OrdinalIgnoreCase 最安全
// 數值
1234.5.ToString("C") // "$1,234.50" (貨幣)
1234.5.ToString("F2") // "1234.50" (固定小數)
1234.5.ToString("N") // "1,234.50" (千分位)
0.1234.ToString("P") // "12.34 %" (百分比)
42.ToString("X") // "2A" (十六進位)
// 日期時間
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") // "2024-01-15 14:30:45"
DateTime.Now.ToString("o") // ISO 8601 "2024-01-15T14:30:45.1234567"
DateTime.Now.ToString("r") // RFC 1123 "Mon, 15 Jan 2024 14:30:45 GMT"
// 自訂格式
42.ToString("D5") // "00042" (補零)
1234.ToString("##,##0") // "1,234" (千分位)
var price = 1234.56;
var date = DateTime.Now;
// 格式指定
$"Price: {price:C2}" // "Price: $1,234.56"
$"Date: {date:yyyy-MM-dd}" // "Date: 2024-01-15"
// 對齊
$"|{name,-10}|{age,3}|" // 左對齊 10 寬、右對齊 3 寬
// 條件運算式
$"Status: {(isActive ? "Active" : "Inactive")}"
// 逐字插值 (C# 11+)
$@"
SELECT * FROM Users
WHERE Name = {name}
AND Age > {minAge}
";
// 1. 預估容量避免擴容
var sb = new StringBuilder(estimatedLength);
// 2. 連結鏈式呼叫
sb.Append("Header").AppendLine().Append(data).AppendLine("Footer");
// 3. 重複使用 (清除而非新建)
sb.Clear(); // 長度歸零,容量保留
sb.Append(newData);
// 4. 附加格式化
sb.AppendFormat("User: {0}, ID: {1}", name, id);
// 5. 移除尾端分隔符
if (sb.Length > 0) sb.Length--; // 移除最後一字元
.NET Core 2.1+ ReadOnlySpan<char> 避免子字串配置:
ReadOnlySpan span = "Hello World".AsSpan();
var world = span.Slice(6); // 無配置,指向原字串記憶體
Console.WriteLine(world.ToString()); // 需要字串時才 ToString()
// 解析不配置
if (int.TryParse(span.Slice(0, 5), out int num)) { }
A: 執行緒安全、安全性(字串內部化、哈希表鍵)、最佳化(字串驻留、共享)、簡化記憶體管理。
string vs String?A: string 是 C# 關鍵字(型別別名),String 是 FCL 型別名稱。功能相同,慣例:變數宣告用 string,靜態方法用 String(如 String.Compare)。
// 悪劣:多次 Replace
s = s.Replace("a", "").Replace("b", "").Replace("c", "");
// 良好:StringBuilder 逐字元檢查
var sb = new StringBuilder(s.Length);
foreach (char c in s) if (!removeSet.Contains(c)) sb.Append(c);
A: 編譯期常值字串自動駐留;執行期可用 string.Intern/IsInterned 手動管理。相同內容駐留字串共用同一實例,ReferenceEquals 為 true。