U+10FFFC 是一个 Unicode 码点,但string
' 的接口并没有直接暴露一系列 Unicode 码点。它的接口公开了一系列 UTF-16 代码单元。这是一个非常低级的文本视图。不幸的是,如此低级的文本视图被嫁接到了最明显和最直观的界面上......我会尽量不要过多地抱怨我不喜欢这种设计,只是说没关系多么不幸,这只是你必须忍受的(悲伤的)事实。
首先,我会建议使用char.ConvertFromUtf32
来获取您的初始字符串。更简单,更具可读性:
var s = char.ConvertFromUtf32(0x10FFFC);
所以,这个字符串Length
不是 1,因为正如我所说,接口处理 UTF-16 代码单元,而不是 Unicode 代码点。U+10FFFC 使用两个 UTF-16 代码单元,所以s.Length
2。所有高于 U+FFFF 的代码点都需要两个 UTF-16 代码单元来表示。
您应该注意,ConvertFromUtf32
不返回char
:char
是 UTF-16 代码单元,而不是 Unicode 代码点。为了能够返回所有 Unicode 代码点,该方法不能返回单个char
. 有时它需要返回两个,这就是为什么它使它成为一个字符串。有时您会发现一些处理int
s 而不是char
because的 APIint
也可用于处理所有代码点(这就是ConvertFromUtf32
作为参数的内容,以及ConvertToUtf32
作为结果产生的内容)。
string
implements IEnumerable<char>
,这意味着当您迭代 a 时,string
每次迭代都会获得一个 UTF-16 代码单元。这就是为什么迭代你的字符串并将其打印出来会产生一些带有两个“东西”的损坏输出。这些是构成 U+10FFFC 表示的两个 UTF-16 代码单元。他们被称为“代理人”。第一个是高/领先替代品,第二个是低/落后替代品。当您单独打印它们时,它们不会产生有意义的输出,因为单独的代理在 UTF-16 中甚至无效,并且它们也不被视为 Unicode 字符。
当您将这两个代理项附加到循环中的字符串时,您可以有效地重建代理项对,并在稍后打印该对,因为其中一个会为您提供正确的输出。
在咆哮的前面,请注意没有人抱怨您在该循环中使用了格式错误的 UTF-16 序列。它创建了一个带有唯一代理项的字符串,但一切都像什么都没发生一样继续进行:该string
类型甚至不是格式良好的UTF-16 代码单元序列的类型,而是任何UTF-16 代码单元序列的类型。
该char
结构提供了处理代理项的静态方法:IsHighSurrogate
、IsLowSurrogate
、IsSurrogatePair
、ConvertToUtf32
和ConvertFromUtf32
。如果你愿意,你可以编写一个迭代 Unicode 字符而不是 UTF-16 代码单元的迭代器:
static IEnumerable<int> AsCodePoints(this string s)
{
for(int i = 0; i < s.Length; ++i)
{
yield return char.ConvertToUtf32(s, i);
if(char.IsHighSurrogate(s, i))
i++;
}
}
然后你可以像这样迭代:
foreach(int codePoint in s.AsCodePoints())
{
// do stuff. codePoint will be an int will value 0x10FFFC in your example
}
如果您更喜欢将每个代码点作为字符串获取,则将返回类型更改为IEnumerable<string>
并将屈服行更改为:
yield return char.ConvertFromUtf32(char.ConvertToUtf32(s, i));
使用该版本,以下内容按原样工作:
foreach(string codePoint in s.AsCodePoints())
{
Console.WriteLine(codePoint);
}