为什么C#Unicode范围覆盖有限的范围(最多0xFFFF)?

为什么C#Unicode范围覆盖有限的范围(最多0xFFFF)?,c#,unicode,encoding,utf-8,C#,Unicode,Encoding,Utf 8,我对C#UTF8编码感到困惑 假设这些“事实”是正确的: Unicode是定义每个字符的“协议” UTF-8定义了“实现”——如何存储这些字符 Unicode定义从0x0000到0x10FFFF()的字符范围 根据,每个字符的可接受范围为0x0000到0xFFFF。我不明白另一个字符是什么,它位于0xFFFF之上,并且是在Unicode协议中定义的 与C#相反,当我使用Python编写UTF8文本时,它覆盖了所有预期范围(0x0000到0x10FFFF)。例如: u"\U00010000" #

我对C#UTF8编码感到困惑

假设这些“事实”是正确的:

  • Unicode是定义每个字符的“协议”
  • UTF-8定义了“实现”——如何存储这些字符
  • Unicode定义从0x0000到0x10FFFF()的字符范围
  • 根据,每个字符的可接受范围为0x0000到0xFFFF。我不明白另一个字符是什么,它位于0xFFFF之上,并且是在Unicode协议中定义的

    与C#相反,当我使用Python编写UTF8文本时,它覆盖了所有预期范围(0x0000到0x10FFFF)。例如:

    u"\U00010000"  #WORKING!!!
    
    这对C#不起作用。此外,当我将Python中的字符串
    u“\U00010000”
    (单字符)写入文本文件,然后从C#中读取时,这个单字符文档变成了C#中的两个字符

    为什么??如何修复

    Unicode有所谓的平面()

    如您所见,C#的
    char
    类型仅支持第一个平面,即平面0,即基本多语言平面

    我知道C#使用UTF-16编码,所以看到它不支持
    char
    数据类型中第一个平面以外的代码点,我有点惊讶。(我自己没有遇到过这个问题……)

    这是
    char
    实现中的人为限制,但这是可以理解的。NET的设计者可能不想将自己的字符数据类型的抽象与Unicode定义的抽象联系起来,以防该标准无法继续存在(它已经取代了其他标准)。当然,这只是我的猜测。它只是“使用”UTF-16作为内存表示

    正如您所了解的,UTF-16使用一种技巧将高于0xFFFF的代码点压缩为16位。从技术上讲,这些代码点由2个“字符”组成,即所谓的代理项对。从这个意义上讲,它打破了“一个代码点=一个字符”的抽象

    通过使用
    string
    char
    数组,您完全可以解决这个问题。如果您有更具体的问题,您可以在StackOverflow和其他地方找到大量关于在.NET中使用所有Unicode代码点的信息

    根据C#参考,每个字符的可接受范围为0x0000到0xFFFF。我不明白另一个字符是什么,它位于0xFFFF之上,并且是在Unicode协议中定义的

    不幸的是,C#/.NET
    字符
    并不表示Unicode字符

    字符是0x0000到0xFFFF范围内的16位值,表示一个“UTF-16代码单元”。U+0000–U+D7FF和U+E000–U+FFFF范围内的字符由相同数字的代码单位表示,因此在这里一切正常

    在U+010000到U+10FFFF范围内,使用频率较低的其他字符通过将每个字符表示为两个UTF-16代码单元一起压缩到剩余的空间0xD800–0xDFFF中,因此Python字符串
    “\U00010000”
    的等价物是C#
    “\uD800\uDC00”

    为什么?

    之所以如此疯狂,是因为Windows NT系列本身使用UTF-16LE作为本机字符串编码,因此为了便于互操作,.NET选择了相同的编码。WinNT选择了当时被认为是UCS-2的编码,并且没有任何讨厌的代理代码单元对,因为在早期Unicode只有U+FFFF以下的字符,并且认为这将是所有人都需要的

    如何修复

    没有什么好办法。不幸的是,其他一些基于UTF-16代码单元(Java、JavaScript)的字符串类型的语言开始向字符串中添加方法,以便一次计算一个代码点;但是目前在.NET中没有这样的功能

    通常,您实际上不需要使用适当的代码点项和索引来计算/查找/拆分/排序/etc字符串。但是,如果你真的这么做了,在.NET中,你的日子就不好过了。最后,您必须通过手动遍历每个
    char
    并检查它是否是两个char代理项对的一部分,或者将字符串转换为一个codepoint数组并返回,来重新实现每个通常很简单的方法。不管怎样,这都不是什么好玩的事


    一个更优雅、更实用的选择是发明一台时间机器,这样我们可以将UTF-8设计回溯到1988年,并阻止UTF-16的存在。

    首先,
    char
    是一个16位的值,意味着它最多只能存储0xFFFF。如果要使用该范围以上的字符形式,则需要一个
    字符串和两个字符。“当我使用Python编写UTF8文本时”-您确定它与C中的UTF8相同吗?首先检查一下,使用相同的尾端编码就足够了,不考虑语言。仅供参考(看),也许你只是想使用后者?@DavidG,你在这里说的是C#不支持Unicode协议。因为很明显,两杯拿铁并不等于一杯拿铁。“只是不一样。”西纳特,我检查了两次。我使用
    编码.UTF8
    。已将我的源代码添加到主线程。它不完全正确。Unicode不是协议,UTF8不是实现。Uncode是代码表,与ASCII相同,但为16位。UTF8是一种适合Unicode作为8位ASCII扩展的方法。您可以使用16位形式的Unicode,并且不需要UTF8。Unicode的一个大问题是它一开始必须是32位,而不是16位。因为已经达到了16位的限制,并且有一些技巧,比如UTF16和其他废料,使Unicode成为32位并保持兼容性。有没有关于平面0的语句的引用?OP指的是UTF8,您指的是UTF16和一些平面。@Sinatr认识到这些平面很重要,因为辅助平面中的所有代码点都是使用此代理项对技巧进行编码的。C#作为一种使用UTF-1的语言
    # Python (write):
    import codecs                        
    with codes.open("file.txt", "w+", encoding="utf-8") as f:                        
        f.write(text) # len(text) -> 1
    
    // C# (read): 
    string text = File.ReadAllText("file.txt", Encoding.UTF8); // How I read this text from file.
    Console.Writeline(text.length); // 2