为什么这个string的长度比它里面的字符长呢?

此代码:

string a = "abc"; string b = "A𠈓C"; Console.WriteLine("Length a = {0}", a.Length); Console.WriteLine("Length b = {0}", b.Length); 

输出:

 Length a = 3 Length b = 4 

为什么? 我能想象的唯一的事情就是中文字符长度为2个字节, .Length方法返回字节数。

其他人都给出了表面的答案,但是也有一个更深的理由:“字符”的数量是一个难以定义的问题,计算起来可能会非常昂贵,而长度属性应该很快。

为什么难以定义? 那么,有几个选项,没有一个比另一个更有效:

  • 代码单元的数量(字节或其他固定大小的数据块; C#和Windows通常使用UTF-16,因此它返回的是两字节块的数量)肯定是相关的,因为计算机仍然需要以这种forms处理数据出于多种目的(写入文件,例如关心字节而不是字符)

  • Unicode代码点的数量是相当容易计算的(尽pipeO(n),因为你必须扫描string代理对),并可能重要的文本编辑器….但实际上不是一样的东西的字符数在屏幕上打印(称为字形)。 例如,一些重音字母可以用两种forms表示:单个码点,或者两个点配对,一个代表字母,另一个代表“给我的伴侣字母添加口音”。 这两个人是两个人物还是一个? 您可以对string进行规范化处理,但并不是所有有效的字母都有一个代码点表示forms。

  • 甚至字形的数量也不同于打印string的长度,这取决于字体和其他因素,并且由于某些字符在许多字体(字距)中被重叠地打印,所以字符在屏幕上的长度不一定总是等于字形长度的总和!

  • 一些Unicode点不是传统意义上的字符,而是某种控制标志。 像字节顺序标记或从右到左的指示符。 这些数字呢?

简而言之,string的长度实际上是一个非常复杂的问题,计算它可能需要大量的CPU时间以及数据表。

而且,这有什么意义呢? 为什么这些指标很重要? 那么,只有你可以回答你的情况,但我个人认为他们通常是不相关的。 我发现限制数据input在逻辑上是通过字节限制来完成的,因为无论如何都需要传输或存储。 限制显示尺寸最好由显示器端软件完成 – 如果消息有100个像素,则适合的字符数取决于数据层软件不知道的字体等。 最后,鉴于unicode标准的复杂性,无论如何,如果您尝试其他方法,您可能会在边缘情况下出现错误。

所以这是一个很难通用的问题。 代码单元的数量是微不足道的计算 – 它只是基础数据数组的长度 – 作为一般规则是最有意义/有用的,有一个简单的定义。

这就是为什么b长度超出了“因为文档如此说”的表面解释。

String.Length属性的文档 :

Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。 原因是一个Unicode字符可能由多个Char表示 。 使用System.Globalization.StringInfo类来处理每个Unicode字符而不是每个字符 。

"A𠈓C"中索引1处"A𠈓C"是代理对象

要记住的关键是代理对代表32位单字符。

你可以试试这个代码,它会返回True

 Console.WriteLine(char.IsSurrogatePair("A𠈓C", 1)); 

Char.IsSurrogatePair方法(string,Int32)

如果s参数包括位置index和index + 1处的相邻字符,并且位置index处的字符数值范围从U + D800到U + DBFF,并且位置index + 1处的字符的数值范围从U + DC00到U + DFFF; 否则,是false

这在String.Length属性中有进一步的解释:

Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。 原因是一个Unicode字符可能由多个Char表示。 使用System.Globalization.StringInfo类来处理每个Unicode字符而不是每个字符。

正如其他答案指出的那样,即使有3个可见字符,它们也用4个char对象表示。 这就是为什么Length是4而不是3。

MSDN指出

Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。

但是,如果您真正想知道的是“文本元素”的数量而不是Char对象的数量,则可以使用StringInfo类。

 var si = new StringInfo("A𠈓C"); Console.WriteLine(si.LengthInTextElements); // 3 

您也可以像这样枚举每个文本元素

 var enumerator = StringInfo.GetTextElementEnumerator("A𠈓C"); while(enumerator.MoveNext()){ Console.WriteLine(enumerator.Current); } 

在string上使用foreach会将两个char对象中的中间“字母”分开,并且打印的结果将不对应于string。

这是因为Length属性返回的是char对象的数量,而不是unicode字符的数量。 在你的情况下,其中一个Unicode字符由多个char对象(Surrogate对象)表示。

Length属性返回此实例中Char对象的数量,而不是Unicode字符的数量。 原因是一个Unicode字符可能由多个Char表示。 使用System.Globalization.StringInfo类来处理每个Unicode字符而不是每个字符。

正如别人所说,这不是string中的字符数,而是Char对象的数量。 字符𠈓是代码点U + 20213。 由于该值在16位字符types的范围之外,因此使用UTF-16编码为代理对D840 DE13

在其他答案中提到了用字符表示长度的方法。 但是应该小心使用,因为可以有许多方法来表示Unicode中的字符。 “à”可以是1个组成字符或2个字符(a +音调符号)。 标准化可能需要像在Twitter的情况下。

你应该读这个
绝对最低限度的每个软件开发人员绝对,积极地必须知道Unicode和字符集(没有借口!)

这是因为length()只适用于不大于U+FFFF Unicode代码点。 这组代码点被称为基本多语言平面 (BMP),只使用2个字节。

BMP之外的Unicode代码点使用4字节代理对以UTF-16表示。

要正确计算字符数(3),请使用StringInfo

 StringInfo b = new StringInfo("A𠈓C"); Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements)); 

好的,在.Net和C#中,所有string都被编码为UTF-16LE 。 一个string被存储为一个字符序列。 每个char封装2个字节或16位的存储。

我们在纸或屏幕上看到的单个字母,字符,字形,符号或标点符号可以被看作单个文本元素。 正如Unicode标准附件#29 UNICODE TEXT SEGMENTATION中所描述的,每个文本元素都由一个或多个代码点表示。 一个详尽的代码列表可以在这里find 。

每个代码点需要编码为二进制内部表示由计算机。 如上所述,每个char存储2个字节。 在U+FFFF或以下的代码点可以存储在一个charU+FFFF以上的代码点作为代理对存储,使用两个字符表示单个代码点。

考虑到我们现在知道我们可以推断出,文本元素可以存储为一个char ,作为两个字符的代理对,或者如果文本元素由多个代码点代表单个字符和代理对的组合。 就像这些不够复杂一样,一些文本元素可以用不同的代码点组合来表示,如Unicode标准附件#15,UNICODE NORMALIZATION FORMS中所描述的 。


插曲

所以,呈现时看起来相同的string实际上可以由不同的字符组合组成。 一个序列(逐字节)比较两个这样的string会检测到一个差异,这可能是意想不到的或不可取的。

您可以重新编码.Netstring。 以便他们使用相同的规范化表单。 一旦标准化,具有相同文本元素的两个string将以相同的方式被编码。 为此,请使用string.Normalize函数。 但是,请记住,一些不同的文本元素看起来相似。 :-s


那么,这对于这个问题意味着什么呢? 文本元素'𠈓'由单个代码点U + 20213 cjk统一表意文字扩展b表示 。 这意味着它不能被编码为一个char ,必须使用两个字符编码为代理对。 这就是为什么string bstring b的一个char

如果您需要可靠(请参阅警告)计算string的文本元素的数量,则应使用System.Globalization.StringInfo类。

 using System.Globalization; string a = "abc"; string b = "A𠈓C"; Console.WriteLine("Length a = {0}", new StringInfo(a).LengthInTextElements); Console.WriteLine("Length b = {0}", new StringInfo(b).LengthInTextElements); 

给出输出,

 "Length a = 3" "Length b = 3" 

如预期。


警告

StringInfoTextElementEnumerator类中的Unicode文本分段的.Net实现应该通常是有用的,并且在大多数情况下,将产生调用者期望的响应。 但是,正如Unicode标准附件#29所述,“匹配用户感知的目标不能总是完全符合,因为单纯的文本并不总是包含足够的信息来明确地决定边界。