您如何从.NET字符串中获取一系列Unicode代码点?

我有一个字符范围限制列表,我需要检查字符串,但.NET中的char类型是UTF-16,因此一些字符变为古怪(代理)对。 因此,当枚举string所有char时,我不会获得32位Unicode代码点,并且某些与高值的比较会失败。

我非常了解Unicode,如果有必要,我可以自己解析字节,但我正在寻找一个C#/ .NET Framework BCL解决方案。 所以……

如何将string转换为32位Unicode代码点的数组( int[] )?

这个答案是不正确的。 请参阅@ Virtlink的答案以获取正确的答案。

 static int[] ExtractScalars(string s) { if (!s.IsNormalized()) { s = s.Normalize(); } List chars = new List((s.Length * 3) / 2); var ee = StringInfo.GetTextElementEnumerator(s); while (ee.MoveNext()) { string e = ee.GetTextElement(); chars.Add(char.ConvertToUtf32(e, 0)); } return chars.ToArray(); } 

注意 :处理复合字符需要规范化。

你问的是代码点 。 在UTF-16(C#的char )中,只有两种可能性:

  1. 该字符来自Basic Multilingual Plane ,由单个代码单元编码。
  2. 该字符在BMP之外,并使用代理高低对代码单元进行编码

因此,假设字符串有效,则返回给定字符串的代码点数组

 public static int[] ToCodePoints(string str) { if (str == null) throw new ArgumentNullException("str"); var codePoints = new List(str.Length); for (int i = 0; i < str.Length; i++) { codePoints.Add(Char.ConvertToUtf32(str, i)); if (Char.IsHighSurrogate(str[i])) i += 1; } return codePoints.ToArray(); } 

代理对🌀和组合字符的示例ñ

 ToCodePoints("\U0001F300 El Ni\u006E\u0303o"); // 🌀 El Niño // { 0x1f300, 0x20, 0x45, 0x6c, 0x20, 0x4e, 0x69, 0x6e, 0x303, 0x6f } // 🌀 E l N in ̃◌ o 

这是另一个例子。 这两个代码点代表一个带有断音重音的第32个音符,两个代理对:

 ToCodePoints("\U0001D162\U0001D181"); // 𝅘𝅥𝅰𝆁 // { 0x1d162, 0x1d181 } // 𝅘𝅥𝅰 𝆁◌ 

当C标准化时 ,它们被分解成一个符头,结合词干,组合旗帜和组合口音 - 断奏,所有代理对:

 ToCodePoints("\U0001D162\U0001D181".Normalize()); // 𝅘𝅥𝅰𝆁 // { 0x1d158, 0x1d165, 0x1d170, 0x1d181 } // 𝅘 𝅥 𝅰 𝆁◌ 

请注意, leppie的解决方案不正确。 问题是关于代码点 ,而不是文本元素 。 文本元素是代码点的组合,它们一起形成单个字素。 例如,在上面的示例中,字符串中的ñ由拉丁语小写n表示,后跟组合波形符̃◌ 。 Leppie的解决方案会丢弃任何无法归一化为单个代码点的组合字符。

似乎不应该比这复杂得多:

 public static IEnumerable Utf32CodePoints( this IEnumerable s ) { bool useBigEndian = !BitConverter.IsLittleEndian; Encoding utf32 = new UTF32Encoding( useBigEndian , false , true ) ; byte[] octets = utf32.GetBytes( s ) ; for ( int i = 0 ; i < octets.Length ; i+=4 ) { int codePoint = BitConverter.ToInt32(octets,i); yield return codePoint; } } 

我想出了Nicholas(和Jeppe)建议的相同方法 ,只是更短:

  public static IEnumerable GetCodePoints(this string s) { var utf32 = new UTF32Encoding(!BitConverter.IsLittleEndian, false, true); var bytes = utf32.GetBytes(s); return Enumerable.Range(0, bytes.Length / 4).Select(i => BitConverter.ToInt32(bytes, i * 4)); } 

枚举就是我所需要的,但获取数组是微不足道的:

 int[] codePoints = myString.GetCodePoints().ToArray();