string.Length 返回的是 UTF-16 编码下的 char 个数,不是人眼可见字符数或字节数;如 "??".Length 为 4,"café"(含组合符).Length 为 5,但视觉上分别为 1 和 4 个字符。

字符串.Length 返回的是什么?
它返回的是 char 的个数,不是“人眼看到的字数”,也不是 Unicode 字符(grapheme cluster)数。C# 的 string 是 UTF-16 编码,一个中文汉字通常是 1 个 char(如 U+4F60),但某些 emoji 或生僻字(如 U+1F9D0)会占 2 个 char(即一个代理对 surrogate pair)。所以 "??".Length 是 4,不是 1。
常见错误现象:"abc你好".Length 得到 5 —— 这没错,但它掩盖了“显示宽度”或“排版占位”的差异:英文字符通常等宽,中文/emoji 占两倍宽度。
- 如果目标是「前端显示截断」或「控制台对齐」,
.Length不可靠 - 如果目标是「协议传输长度限制」或「内存占用估算」,
.Length是合理起点 - 若需兼容 .NET Core 3.0+,可考虑
StringInfo或TextElementEnumerator按视觉字符计数
按显示宽度算长度:用 StringInfo 获取文本元素
当你要模拟终端/编辑器里“光标移动一次跳一格”的行为,就得按 Unicode 文本元素(text element)来切分,而不是 char。比如 "café" 中的 é(带重音)可能由 'e' + '\u0301' 组成,.Length 算作 5,但人眼认作 4 个字母。
实操建议:
- 用
StringInfo.GetUnicodeCategory()不行 —— 它只判单个char类别,无法处理组合序列 - 正确做法:用
StringInfo.GetTextElementEnumerator(str)遍历,每次MoveNext()跳过一个用户感知的“字符” - 示例:
var len = StringInfo.ParseCombiningCharacters("a\u0301").Length返回 1(不是 2) - 注意:这个 API 在 .NET Framework 中性能较差,频繁调用需缓存结果
中英文混合时的显示宽度估算(非精确但够用)
没有统一标准能 100% 正确映射“字符→像素宽度”,但多数场景下可以按惯例估算:ASCII 字符(U+0020–U+007E)算 1 单位宽,其余(含中文、日文、emoji、拉丁扩展字符)算 2 单位宽。这是很多终端、富文本控件(如 WPF TextBlock)内部采用的简化规则。
你可以这样写一个轻量函数:
static int GetDisplayWidth(string s) {
return s.Sum(c => char.IsAscii(c) ? 1 : 2);
}注意点:
-
char.IsAscii()是 .NET 5+ 才有;旧版本可用c >= 0x20 && c - 它把全角 ASCII(如 A)也判为 1 —— 实际应为 2,但这类字符极少见,权衡后可忽略
- 遇到
"??"这类 ZWJ 连接符组合,仍会高估(返回 8),但比.Length的 4 更贴近显示效果
为什么不能直接用 Encoding.UTF8.GetByteCount()?
有人想用字节数代替长度,觉得“UTF-8 下中文占 3 字节,英文占 1 字节,加起来就是‘实际大小’”。这在文件存储或网络传输中有意义,但和“字符数”或“显示宽度”完全不是一回事。
典型误用场景:
- 做日志截断:
Encoding.UTF8.GetByteCount(s) > 1024→ 截掉后面字节 → 可能产生非法 UTF-8 序列,解码报错System.Text.DecoderFallbackException - 做数据库字段长度校验:SQL Server 的
nvarchar按char数计长,不是字节数 - 真正需要字节长度时,必须配合安全截断逻辑,例如先用
Encoding.UTF8.GetBytes()转数组,再找最后一个完整字符边界
最常被忽略的一点:同一个字符串,在不同编码下字节数完全不同。拿 GetByteCount() 当“长度”用,等于默认绑定了 UTF-8 —— 而你的程序未必始终走 UTF-8 流水线。


















