【问题标题】:.NET String object and invalid Unicode code points.NET 字符串对象和无效的 Unicode 代码点
【发布时间】:2015-01-18 21:32:26
【问题描述】:

.NET String 对象是否可能包含无效的 Unicode 代码点?

如果是,这是怎么发生的(以及如何确定字符串是否包含此类无效字符)?

【问题讨论】:

  • 通常是错误的 pinvoke。避免问 XY 问题。
  • @HansPassant 我不能只发布我的第一个问题。对于太短的问题有某种验证,所以我还添加了第二部分,我认为这是相关的。
  • @HansPassant:那么,如果没有以错误的方式进行本地化,就没有无与伦比的代理人?未分配的代码点呢?

标签: .net string unicode


【解决方案1】:

虽然@DPenner 给出的响应非常好(我以它为起点),但我想提供一些其他细节。 除了我认为是无效字符串的明显标志的孤立代理之外,字符串总是有可能包含未分配的代码点,并且这种情况不能被 .NET Framework 视为错误,因为新字符总是添加到 Unicode 标准中,例如,参见 Unicode http://en.wikipedia.org/wiki/Unicode#Versions 的版本。而且,为了让事情更清楚,这个调用Char.GetUnicodeCategory(Char.ConvertFromUtf32(0x1F01C), 0);在使用.NET 2.0时返回UnicodeCategory.OtherNotAssigned,但在使用.NET 4.0时它会返回UnicodeCategory.OtherSymbol

除此之外,还有另一个有趣的点:甚至 .NET 类库方法都没有就如何处理 Unicode 非字符和不成对的代理字符达成一致。例如:

  • 未配对的代理字符
    • System.Text.Encoding.Unicode.GetBytes("\uDDDD"); - 返回{ 0xfd, 0xff}Replacement character 的编码,即认为数据无效。
    • "\uDDDD".Normalize(); - 抛出异常信息“Invalid Unicode code point found at index 0.”,即认为数据无效。
  • 非字符代码点
    • System.Text.Encoding.Unicode.GetBytes("\uFFFF"); - 返回{0xff, 0xff},即认为数据有效。
    • "\uFFFF".Normalize(); - 抛出异常消息“Invalid Unicode code point found at index 0.”,即数据被视为无效。

下面是一种在字符串中搜索无效字符的方法:

/// <summary>
/// Searches invalid charachters (non-chars defined in Unicode standard and invalid surrogate pairs) in a string
/// </summary>
/// <param name="aString"> the string to search for invalid chars </param>
/// <returns>the index of the first bad char or -1 if no bad char is found</returns>
static int FindInvalidCharIndex(string aString)
{
    int ch;
    int chlow;

    for (int i = 0; i < aString.Length; i++)
    {
        ch = aString[i];
        if (ch < 0xD800) // char is up to first high surrogate
        {
            continue;
        }
        if (ch >= 0xD800 && ch <= 0xDBFF)
        {
            // found high surrogate -> check surrogate pair
            i++;
            if (i == aString.Length)
            {
                // last char is high surrogate, so it is missing its pair
                return i - 1;
            }

            chlow = aString[i];
            if (!(chlow >= 0xDC00 && chlow <= 0xDFFF))
            {
                // did not found a low surrogate after the high surrogate
                return i - 1;
            }

            // convert to UTF32 - like in Char.ConvertToUtf32(highSurrogate, lowSurrogate)
            ch = (ch - 0xD800) * 0x400 + (chlow - 0xDC00) + 0x10000;
            if (ch > 0x10FFFF)
            {
                // invalid Unicode code point - maximum excedeed
                return i;
            }
            if ((ch & 0xFFFE) == 0xFFFE)
            {
                // other non-char found
                return i;
            }
            // found a good surrogate pair
            continue;
        }

        if (ch >= 0xDC00 && ch <= 0xDFFF)
        {
            // unexpected low surrogate
            return i;
        }

        if (ch >= 0xFDD0 && ch <= 0xFDEF)
        {
            // non-chars are considered invalid by System.Text.Encoding.GetBytes() and String.Normalize()
            return i;
        }

        if ((ch & 0xFFFE) == 0xFFFE)
        {
            // other non-char found
            return i;
        }
    }

    return -1;
}

【讨论】:

  • 这很有趣,但据我所知,这种行为实际上符合 Unicode 标准。由于GetBytes() 是一种转换方法,因此当存在非法字节序列时,required 以某种方式发出错误信号。对于"\uDDDD".Normalize(),字符串首先是无效的,所以Unicode 对此没有什么可说的。使用"\uFFFF".Normalize()section 12 of TR 15 专门允许进程在具有未分配字符的字符串上中止。
  • 您对代理对不公平,是的。您使用System.Text.Encoding.Unicode,但这只是获取new System.Text.UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: false) 的一种便捷方式,该new System.Text.UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: false) 被记录为提供替换字符。如果您使用 new System.Text.UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true) 代替,则会在未配对的代理案例中出现异常。
【解决方案2】:

是的,这是可能的。根据微软的文档,.NET String 很简单

String 对象是表示字符串的 System.Char 对象的顺序集合。

而 .NET Char

将字符表示为 UTF-16 代码单元。

综合起来,这意味着 .NET 字符串只是 UTF-16 代码单元的序列,无论它们是否是符合 Unicode 标准的有效字符串。发生这种情况的方式有很多,我能想到的比较常见的有:

  • 一个非 UTF-16 字节流被错误地放入 String 对象而没有正确转换。
  • String 对象在代理对之间被拆分。
  • 有人故意包含这样一个字符串来测试系统的健壮性。

因此,以下 C# 代码是完全合法的并且可以编译:

class Test
    static void Main(){
        string s = 
            "\uEEEE" + // A private use character
            "\uDDDD" + // An unpaired surrogate character
            "\uFFFF" + // A Unicode noncharacter
            "\u0888";  // A currently unassigned character       
        System.Console.WriteLine(s); // Output is highly console dependent
    }
}

【讨论】:

    【解决方案3】:

    .NET 和 C# 中的所有字符串都使用 UTF-16 编码,但有一个例外(取自 Jon Skeet's blog):

    ...有两种不同的表示形式:大多数时候,UTF-16 已使用,但属性构造函数参数使用 UTF-8...

    【讨论】:

      【解决方案4】:

      我认为 .NET 字符串中的无效代码点只有在有人将单个元素设置为高或低代理时才会发生。也可能有人从有效代理对中删除一个高或低代理,后者不仅可以通过删除元素发生,还可以通过更改元素的值发生。在我看来,答案是“是”,它可能会发生,唯一的原因可能是字符串中有一个孤立的高或低代理。你有一个真实的示例字符串吗?把它贴在这里,我可以检查出什么问题。

      顺便说一句。对于 UTF-16 文件也是如此。这有可能发生。对于具有 0xFFEE BOM 的 utf-16LE 文件,请确保您的第一个字符不是 0,因为这样您的前 4 个字节是 0xFFFE0000,这肯定会被解释为 utf-32LE BOM 而不是 utf-16LE BOM!

      【讨论】:

      • 不,我没有样品,但如果可能的话,我想看看。
      • 我有这样的文件,但是我怎么能把它上传到这里呢?我留下了一个指向我的 http 服务器的链接,显示了部分文件的屏幕截图:link to corrupt utf-16 file
      猜你喜欢
      • 2021-11-08
      • 2016-02-02
      • 2010-10-15
      • 1970-01-01
      • 2017-07-25
      • 1970-01-01
      • 2020-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多