C#:读取字符串的第一个字符,当该字符的 unicode 值大于 65535 时

C#: read the first char of a string, when that char's unicode value is > 65535

我有一个 C# 方法需要检索字符串的第一个字符,并查看它是否存在于包含特定 unicode 字符(所有从右到左的字符)的 HashSet 中。

所以我在做

var c = str[0];

然后检查哈希集。

问题是此代码不适用于第一个字符的代码点大于 65535 的字符串。

我实际上创建了一个循环,遍历从 0 到 70,000 的所有数字(最高 RTL 代码点大约是 68,000,所以我四舍五入),我从数字创建一个字节数组,并使用

Encoding.UTF32.GetString(intValue);

用这个字符创建一个字符串。然后我将它传递给在 HashSet 中搜索的方法,但该方法失败了,因为当它获取

str[0]

那个值永远不是它应该的值。

我做错了什么?

一个String是一个UTF-16编码单元序列,一个或两个编码一个Unicode代码点。如果要从字符串中获取代码点,则必须迭代字符串中的代码点。 "character" 也是一个基本代码点,后跟一系列零个或多个组合代码点 ("combining characters")。

// Use a HashSet<String>

var itor = StringInfo.GetTextElementEnumerator(s);
while (itor.MoveNext()) {
    var character = itor.GetTextElement();
    // find character in your HashSet
}

如果不需要考虑组合代码点,可以将它们清除。 (但它们在某些语言中非常重要。)

我不确定我是否理解您的问题,一小段代码可能会有用。当你有像 'var c = str[0]' 这样的行时,假设 'str' 是一个字符串,那么 c 将是一个字符,它被编码为 UTF16。因为这个 c 永远不会大于 (2^16 - 1)。 Unicode 字符可以比那个大,但当出现这种情况时,它们会被编码为跨越多个 'character' 位置。在 UTF-16 的情况下,'first' 字符可能占用 1 或 2 个 16 位值。

对于以后看到这个问题并对我最终得到的解决方案感兴趣的任何人 - 这是我的方法,它根据字符串中的第一个字符决定是否应显示字符串 RTL 或 LTR。它考虑了 UTF-16 代理项对。

感谢 Tom Blodget 为我指明了正确的方向。

if (string.IsNullOrEmpty(str)) return null;

var firstChar = str[0];
if (firstChar >= 0xd800 && firstChar <= 0xdfff)
{
    // if the first character is between 0xD800 - 0xDFFF, this is the beginning
    // of a UTF-16 surrogate pair. there MUST be one more char after this one,
    // in the range 0xDC00-0xDFFF. 
    // for the very unreasonable chance that this is a corrupt UTF-16 string
    // and there is no second character, validate the string length
    if (str.Length == 1) return FlowDirection.LeftToRight;

    // convert surrogate pair to a 32 bit number, and check the codepoint table
    var highSurrogate = firstChar - 0xd800;
    var lowSurrogate = str[1] - 0xdc00;
    var codepoint = (highSurrogate << 10) + (lowSurrogate) + 0x10000;

    return _codePoints.Contains(codepoint)
        ? FlowDirection.RightToLeft
        : FlowDirection.LeftToRight;
}
return _codePoints.Contains(firstChar)
    ? FlowDirection.RightToLeft
    : FlowDirection.LeftToRight;