使用.Net的大于2个字节的unicode字符

Ear*_*rlz 12 .net c# unicode utf-16 char

我正在使用此代码生成 U+10FFFC

var s = Encoding.UTF8.GetString(new byte[] {0xF4,0x8F,0xBF,0xBC});
Run Code Online (Sandbox Code Playgroud)

我知道它是供私人使用的,但它确实显示了一个单一字符,就像我在展示它时所期望的那样.操作此unicode字符时出现问题.

如果我以后这样做:

foreach(var ch in s)
{
    Console.WriteLine(ch);
}
Run Code Online (Sandbox Code Playgroud)

它不打印单个字符,而是打印两个字符(即字符串显然由两个字符组成).如果我改变我的循环,将这些字符添加回空字符串,如下所示:

string tmp="";
foreach(var ch in s)
{
    Console.WriteLine(ch);
    tmp += ch;
}
Run Code Online (Sandbox Code Playgroud)

在这结束时,tmp将只打印一个字符.

到底发生了什么?我认为它char包含一个unicode字符,除非我正在转换为字节,否则我永远不必担心字符有多少字节.我真正的用例是我需要能够检测字符串中何时使用非常大的unicode字符.目前我有这样的事情:

foreach(var ch in s)
{
    if(ch>=0x100000 && ch<=0x10FFFF)
    {
        Console.WriteLine("special character!");
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,由于这种非常大的字符分裂,这不起作用.如何修改它以使其工作?

R. *_*des 36

U + 10FFFC是一个Unicode代码点,但是它string的接口不会直接暴露一系列Unicode代码点.它的接口公开了一系列UTF-16代码单元.这是一个非常低级别的文本视图.非常遗憾的是,这种低级别的文本视图被嫁接到最明显和最直观的界面上......我会尽量不去嘲笑我不喜欢这种设计,只是说无所谓多么不幸,这只是一个你必须忍受的(悲伤)事实.

首先,我建议char.ConvertFromUtf32用来获取你的初始字符串.更简单,更易读:

var s = char.ConvertFromUtf32(0x10FFFC);
Run Code Online (Sandbox Code Playgroud)

所以,这个字符串Length不是1,因为正如我所说,接口处理的是UTF-16代码单元,而不是Unicode代码点.U + 10FFFC使用两个UTF-16代码单元,因此s.Length是2. U + FFFF以上的所有代码点都需要两个UTF-16代码单元来表示它们.

您应该注意,ConvertFromUtf32不返回a char:char是UTF-16代码单元,而不是Unicode代码点.为了能够返回所有Unicode代码点,该方法不能返回单个char.有时它需要返回两个,这就是为什么它使它成为一个字符串.有时候你会发现一些处理ints的API 而不是char因为它们int也可以用来处理所有的代码点(这就是ConvertFromUtf32作为参数的结果,以及ConvertToUtf32结果产生的结果).

string实现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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以迭代:

foreach(int codePoint in s.AsCodePoints())
{
     // do stuff. codePoint will be an int will value 0x10FFFC in your example
}
Run Code Online (Sandbox Code Playgroud)

如果您希望将每个代码点作为字符串,而是将返回类型更改为IEnumerable<string>和yield行:

yield return char.ConvertFromUtf32(char.ConvertToUtf32(s, i));
Run Code Online (Sandbox Code Playgroud)

使用该版本,以下工作原样:

foreach(string codePoint in s.AsCodePoints())
{
     Console.WriteLine(codePoint);
}
Run Code Online (Sandbox Code Playgroud)