String.Length 的陷阱string.Length 回傳 Char 物件數量(UTF-16 代碼單元),非顯示字元數、非位元組數。中文、表情符號、補充平面字元可能佔 1 或 2 個 Char,導致長度計算與顯示寬度不符。
private int GetLength(string orgStr)
{
int len = orgStr.Length;
char[] chars = orgStr.ToCharArray();
for (int i = 0; i < chars.Length; i++)
{
// 簡易判斷:Unicode 碼點 > 255 視為雙位元組字元
if (Convert.ToInt32(chars[i]) > 255)
len++;
}
return len;
}
private string MutiSubString(string orgStr, int length, ref string afterStr)
{
int start = 0;
int single = 0;
char[] chars = orgStr.ToCharArray();
int end = chars.Length;
for (int i = 0; i < chars.Length; i++)
{
if (Convert.ToInt32(chars[i]) > 255)
start += 2;
else
{
start += 1;
single++;
}
if (start >= length)
{
// 邊界修正:避免截斷雙位元組字元
if (end % 2 == 0)
{
end = (single % 2 == 0) ? i + 1 : i;
}
else
end = i + 1;
break;
}
}
string result = orgStr.Substring(0, end);
afterStr = orgStr.Remove(0, end);
return result;
}
string after;
string result = MutiSubString("abc漢字字符", 5, ref after);
Console.WriteLine($"Result: '{result}'"); // "abc漢"
Console.WriteLine($"After: '{after}'"); // "字字符"
邏輯解析:
a (1) → 累計 1b (1) → 累計 2c (1) → 累計 3漢 (2) → 累計 5 → 滿足長度 5,截取索引 4 (含「漢」)| 問題 | 說明 | 改進 |
|---|---|---|
| 簡易 >255 判斷 | 無法區分希臘字母、西里爾字母、表情符號、補充平面字元 | 使用 Char.IsSurrogate、StringInfo、Rune (NET Core 3.0+) |
| 截取邊界修正複雜 | 奇偶邏輯難維護 | 改用 StringInfo.ParseCombiningCharacters 取得真實字元邊界 |
| 效能 | 每次 ToCharArray、Convert.ToInt32 | 改用 Span<char>、Rune.DecodeFromUtf16 |
using System.Text;
// 真實字元計數(使用者感知字元)
int graphemeCount = StringInfo.GetTextElementCount("abc漢字字符"); // 7
// 精確截取前 N 個字素簇
string SubstringByGrapheme(string s, int count)
{
var enumerator = StringInfo.GetTextElementEnumerator(s);
int taken = 0;
while (enumerator.MoveNext() && taken < count)
taken++;
return s.Substring(0, enumerator.ElementIndex + enumerator.ElementLength);
}
// 顯示寬度計算(終端機/控制台)
// 需引入 Nerdbank.Streams 或自行對照 Unicode 東亞寬度屬性
原始 MutiSubString 以 char > 255 近似判斷雙位元組字元,解決基本中英混排截取需求。現代 .NET 應改用 StringInfo 處理字素簇、Rune 處理 Unicode 標量值、UnicodeEastAsianWidth 計算顯示寬度,構建正確的國際化字串處理基礎設施。