字元大小寫轉換看似簡單,實則涉及 ASCII 碼結構、Unicode 字元屬性、文化特定規則 (土耳其語 I/ı)、正規化形式等深層議題。本文從面試經典題目切入,層層深入至生產級最佳實務。
// 僅適用 ASCII A-Z / a-z
public static string SwapCaseAscii(string input)
{
char[] arr = input.ToCharArray();
for (int i = 0; i < arr.Length; i++)
{
char c = arr[i];
if (c >= 'A' && c <= 'Z') arr[i] = (char)(c | 0x20); // 大寫→小寫:設定 bit 5
else if (c >= 'a' && c <= 'z') arr[i] = (char)(c & ~0x20); // 小寫→大寫:清除 bit 5
}
return new string(arr);
}
'A' (0x41) | 0x20 = 'a' (0x61)。char.ToUpper/ToLower 與 string.ToUpper/ToLower
// 當前文化特性 (CurrentCulture) - 可能受系統語言影響
string upper = input.ToUpper(); // 等同 ToUpper(CultureInfo.CurrentCulture)
string lower = input.ToLower();
// 不變文化特性 (InvariantCulture) - 固定規則,適合檔案路徑、XML 標籤、程式識別碼
string upperInv = input.ToUpperInvariant(); // 等同 ToUpper(CultureInfo.InvariantCulture)
// 字元層級
char upperChar = char.ToUpper(c, CultureInfo.InvariantCulture);
關鍵差異:
| 場景 | 推薦 | 原因 |
|---|---|---|
| 使用者介面顯示 | CurrentCulture | 尊重使用者語言習慣 |
| 檔案路徑、註冊表鍵、XML/JSON 鍵名、程式內部識別碼 | InvariantCulture | 跨機器、跨語言結果一致 |
| 安全性比較 (密碼、Token) | OrdinalIgnoreCase | 不做語言學轉換,純位元組比較 |
// 德語 ß (U+00DF) 大寫為 "SS" (兩個字元)
"straße".ToUpperInvariant() // "STRASSE" 長度變長!
// 希臘語 Sigma 有兩種小寫形式
"Σ".ToLowerInvariant() // "σ" (詞中) 或 "ς" (詞尾) - 需語境
// 土耳其語 (tr-TR) 有四個 I
// 大寫:I (U+0049) 、İ (U+0130)
// 小寫:ı (U+0131) 、i (U+0069)
var tr = new CultureInfo("tr-TR");
"I".ToLower(tr) // "ı" (點失落)
"İ".ToLower(tr) // "i" (保留點)
"ı".ToUpper(tr) // "I"
"i".ToUpper(tr) // "İ"
// 使用 InvariantCulture 避免驚喜
"I".ToLowerInvariant() // "i"
教訓:任何涉及系統識別碼、快取鍵、路徑的大小寫轉換,**必須指定 CultureInfo.InvariantCulture**。
// 錯誤:分配新字串、文化特性陷阱、效能差
if (a.ToUpper() == b.ToUpper()) ...
// 正確:OrdinalIgnoreCase - 直接比較碼位,忽略大小寫 (僅 ASCII 範圍)
if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase)) ...
// 正確:CurrentCultureIgnoreCase - 語言學比較 (較慢)
if (string.Equals(a, b, StringComparison.CurrentCultureIgnoreCase)) ...
StringBuilder + Span
// 避免多次字串連接分配
public static string SwapCaseFast(string input)
{
var sb = new StringBuilder(input.Length);
foreach (char c in input)
{
if (char.IsUpper(c)) sb.Append(char.ToLowerInvariant(c));
else if (char.IsLower(c)) sb.Append(char.ToUpperInvariant(c));
else sb.Append(c);
}
return sb.ToString();
}
// .NET 6+ Span 版本 (零分配,就地轉換)
public static void SwapCaseInPlace(Span<char> buffer)
{
for (int i = 0; i < buffer.Length; i++)
{
char c = buffer[i];
if (char.IsUpper(c)) buffer[i] = char.ToLowerInvariant(c);
else if (char.IsLower(c)) buffer[i] = char.ToUpperInvariant(c);
}
}
ToUpperInvariant vs ToUpper + Normalize
// 組合字元 (如 é = e + ́)
string composed = "é"; // U+00E9 (NFC)
string decomposed = "e\u0301"; // U+0065 U+0301 (NFD)
composed.ToUpperInvariant() // "É" (U+00C9)
decomposed.ToUpperInvariant() // "É" (U+0045 U+0301) - 仍為分解形式
// 最佳實務:先正規化再轉換
string NormalizeAndUpper(string s) => s.Normalize(NormalizationForm.FormC).ToUpperInvariant();
c ^ 0x20 (確認輸入範圍)。ToUpper(CurrentCulture) / ToLower(CurrentCulture)。ToUpperInvariant() / ToLowerInvariant()。string.Equals(a, b, StringComparison.OrdinalIgnoreCase) (系統) 或 CurrentCultureIgnoreCase (語言)。StringBuilder 或 Span<char> 就地修改。大小寫轉換是「簡單問題、複雜答案」的經典案例。理解 ASCII 位運算固然展現底層功力,但在 Unicode 時代,正確使用 CultureInfo.InvariantCulture、StringComparison.OrdinalIgnoreCase、NormalizationForm 才是專業開發者的分水嶺。下次面試若再遇到這題,不妨先問:「這個系統的目標語言與用途是什麼?」