C#中字符的返回代码点

25

如何返回一个字符的Unicode代码点?例如,如果输入是“A”,则输出应为“U+0041”。理想情况下,解决方案应该处理代理对

这里的代码点是指根据Unicode实际的代码点,与代码单元不同(UTF8具有8位代码单元,UTF16具有16位代码单元,而UTF32具有32位代码单元,在后一种情况下,值等于代码点,在考虑字节序时)。


2
这个问题的措辞非常不当。“返回字符的‘Unicode’”没有意义,而且实际上是无稽之谈。您的示例清楚地说明了您实际想要什么,但标题需要重新制定。请重新修改标题。 - tchrist
1
谢谢。我已经给你点赞以示感激。 - tchrist
7个回答

15

以下代码将一个string输入的码点写入控制台:

string input = "\uD834\uDD61";

for (var i = 0; i < input.Length; i += char.IsSurrogatePair(input, i) ? 2 : 1)
{
    var codepoint = char.ConvertToUtf32(input, i);

    Console.WriteLine("U+{0:X4}", codepoint);
}

输出:

U+1D161

.NET中的字符串采用UTF-16编码,组成字符串的char值需要首先转换为UTF-32。


1
这并不是将其转换为UTF-32,而是将代码点作为整数返回,UTF-32是一种编码,而不是整数。这种方法命名会传播与微软将UTF-16LE编码标记为“unicode”相同的混淆。 - Esailija
2
@Esailija:我不确定什么更令人困惑:使用名为“ConvertToUtf32”的方法将其转换为Unicode代码点,还是将其转换为UTF-32并将结果视为Unicode代码点。最终,这可能只是纠结于细节。 - dtb
1
你不能将转换为实际UTF-32的结果视为代码点,你需要从编码中解码代码点,就像你从UTF-16或UTF-8解码一样,只是更简单。但我可以理解为什么这会被视为吹毛求疵 :P - Esailija

12

很容易,因为C#中的字符实际上是UTF16代码点:

char x = 'A';
Console.WriteLine("U+{0:x4}", (int)x);
为了回应评论,C#中的char是一个16位数字,可以保存UTF16编码点。超过16位空间的编码点无法用C#字符表示。C#中的字符不具备可变宽度。然而,字符串可以将两个跟随的字符作为代码单元,组成一个UTF16编码点。如果您有一个字符串输入并且包含超过16位空间的字符,您可以使用char.IsSurrogatePairChar.ConvertToUtf32,正如另一个答案所建议的:
string input = ....
for(int i = 0 ; i < input.Length ; i += Char.IsSurrogatePair(input,i) ? 2 : 1)
{
    int x = Char.ConvertToUtf32(input, i);
    Console.WriteLine("U+{0:X4}", x);
}

9
它们是Unicode代码单元,而不是代码点。那么需要多个代码单元的字符呢? - President James K. Polk
2
@driis:我没有给你投反对票,我只是提供了一个澄清的观点。 - President James K. Polk
@dtb:不是Char字符,我指的是Unicode字符。我讨厌整个Unicode术语,因为它似乎是设计来混淆人们的。我仍然认为这个答案中“point”和“unit”被交换了。 - President James K. Polk
2
@Qaesar 小写字母 a ('a') 是 U+0061,大写字母 A ('A') 是 U+0041 - Esailija
2
如果我们让你感到困惑,我们很抱歉。尽管乍一看Unicode编码似乎并不那么复杂,但实际上它是有点复杂的。这个答案中的代码或者@dtb发表的答案都可以为您提供良好效果。如果你想了解更多相关信息,我可以推荐http://www.joelonsoftware.com/articles/Unicode.html。 - driis
显示剩余6条评论

11
在 .NET Core 3.0 或更高版本中,您可以使用 Rune 结构体:(链接)
// Note that  and  are encoded using surrogate pairs
// but A, B, C and ✋ are not
var runes = "ABC✋".EnumerateRunes();

foreach (var r in runes)
    Console.Write($"U+{r.Value:X4} ");
        
// Writes: U+0041 U+0042 U+0043 U+270B U+1F609 U+1F44D

4

C#中的char无法存储Unicode代码点,因为char只有2个字节,而Unicode代码点通常超过这个长度。解决方法是将代码点表示为一系列字节(作为字节数组或“压缩”为32位原始值)或字符串。接受的答案将其转换为UTF32,但这并不总是理想的。

以下是我们用于将字符串拆分为其Unicode代码点组件并保留本机UTF-16编码的代码。结果是可枚举的,可用于在C#/.NET中本地比较(子)字符串:

    public class InvalidEncodingException : System.Exception
    { }

    public static IEnumerable<string> UnicodeCodepoints(this string s)
    {
        for (int i = 0; i < s.Length; ++i)
        {
            if (Char.IsSurrogate(s[i]))
            {
                if (s.Length < i + 2)
                {
                    throw new InvalidEncodingException();
                }
                yield return string.Format("{0}{1}", s[i], s[++i]);
            }
            else
            {
                yield return string.Format("{0}", s[i]);
            }
        }
    }
}

2

实际上,@Yogendra Singh的回答有一定的价值,目前是唯一一个得到负评的答案。可以这样完成这项工作。

    public static IEnumerable<int> Utf8ToCodePoints(this string s)
    {
        var utf32Bytes = Encoding.UTF32.GetBytes(s);
        var bytesPerCharInUtf32 = 4;
        Debug.Assert(utf32bytes.Length % bytesPerCharInUtf32 == 0);
        for (int i = 0; i < utf32bytes.Length; i+= bytesPerCharInUtf32)
        {
            yield return BitConverter.ToInt32(utf32bytes, i);
        }
    }

测试通过

    var surrogatePairInput = "abc";
    Debug.Assert(surrogatePairInput.Length == 5);
    var pointsAsString = string.Join(";" , 
        surrogatePairInput
        .Utf8ToCodePoints()
        .Select(p => $"U+{p:X4}"));
    Debug.Assert(pointsAsString == "U+0061;U+0062;U+0063;U+1F4A9");

这个例子是相关的,因为“狗屎”被表示为代理对。


作为改进的一点,您可以直接获取utf32字节,而不是获取utf8字节然后将其转换为utf32。 - Chris
另外,你提到的答案得分为负数的原因是该方法只接受char作为参数,这意味着它永远无法给你超过两个字节的信息。而你的改进非常大,因为你实际上解析了一个字符串,而不是字符。 - Chris
谢谢@Chris。我简化了这个方法。 - Călin Darie

-1

我在MSDN论坛上找到了一个小方法。希望这能帮到你。

    public int get_char_code(char character){ 
        UTF32Encoding encoding = new UTF32Encoding(); 
        byte[] bytes = encoding.GetBytes(character.ToString().ToCharArray()); 
        return BitConverter.ToInt32(bytes, 0); 
    } 

4
这个表达式是否可能返回 (int)character 以外的结果?如果 character 是代理对的前半部分,会发生什么? - dtb
@dtb(非常晚的回答,我知道)。这段代码有趣的地方在于它使用了UTF32Encoding,但由于该方法只接受一个char,因此它没有任何效果,与(int) character相同,但比强制转换慢得多。实际上,character.ToString().ToCharArray()将始终返回一个仅包含一个项目(大小为2个字节)的数组,并且BitConverter永远不会返回大于65535的值。原则上是个好主意,但以这种方式呈现是无用的。 - Abel

-1
public static string ToCodePointNotation(char c)
{

    return "U+" + ((int)c).ToString("X4");
}

Console.WriteLine(ToCodePointNotation('a')); //U+0061

@Qaesar 小写字母 a ('a') 是 U+0061,大写字母 A ('A') 是 U+0041 - Esailija
如果 Char.IsSurrogate(c),则应该抛出异常,因为这样的代码单元不能被视为代码点值,因此没有代码点表示法。 - Tom Blodget
1
这个答案显然是不正确的,你不能假设在C#中存在一个字符和UTF-16代码点之间的一对一映射,因为实际上并不存在这样的映射关系。 - Mahmoud Al-Qudsi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接