将 Unicode 代理项对转换为文字字符串
Convert Unicode surrogate pair to literal string
我正在尝试将一个高位 Unicode 字符从一个字符串读入另一个字符串。为了简洁起见,我将简化我的代码,如下所示:
public static void UnicodeTest()
{
var highUnicodeChar = ""; //Not the standard A
var result1 = highUnicodeChar; //this works
var result2 = highUnicodeChar[0].ToString(); // returns \ud835
}
当我直接将highUnicodeChar
赋给result1
时,它保留了</code>的字面值。当我尝试通过索引访问它时,它 returns <code>\ud835
。据我了解,这是一对用于表示 UTF-32 字符的 UTF-16 字符代理项。我很确定这个问题与尝试将 char
隐式转换为 string
.
有关
最后,我希望 result2
产生与 result1
相同的值。我该怎么做?
在 Unicode, you have code points. These are 21 bits long. Your character , Mathematical Bold Capital A
中,代码点为 U+1D400。
在 Unicode 编码中,您有 代码单元 。这些是编码的自然单位:8-bit for UTF-8, 16-bit for UTF-16,等等。一个或多个代码单元编码单个代码点。
在 UTF-16 中,形成单个代码点的两个代码单元称为 代理对。代理对用于编码大于 16 位的任何代码点,即 U+10000 及以上。
这在 .NET 中有点棘手,因为 .NET Char
表示单个 UTF-16 代码单元,而 .NET String
是代码单元的集合。
因此您的代码点 (U+1D400) 不能容纳在 16 位中并且需要一个代理项对,这意味着您的字符串中有两个代码单元:
var highUnicodeChar = "";
char a = highUnicodeChar[0]; // code unit 0xD835
char b = highUnicodeChar[1]; // code unit 0xDC00
这意味着当您像这样索引字符串时,您实际上只得到代理项对的一半。
您可以使用 IsSurrogatePair 来测试代理对。例如:
string GetFullCodePointAtIndex(string s, int idx) =>
s.Substring(idx, char.IsSurrogatePair(s, idx) ? 2 : 1);
重要的是要注意 Unicode 中变量编码的兔子洞并没有在代码点结束。 字素簇 是 "visible thing" 大多数人在被问到时最终会称其为 "character"。一个字素簇由一个或多个代码点组成:一个基本字符和零个或多个组合字符。组合字符的一个示例是变音符号或您可能想要添加的其他各种 decorations/modifiers。请参阅 this answer 了解组合字符的可怕示例。
要测试组合字符,您可以使用 GetUnicodeCategory 检查封闭标记、非间距标记或间距标记。
您似乎想从用户的角度提取第一个 "atomic" 字符(即第一个 Unicode grapheme cluster) from the highUnicodeChar
string, where an "atomic" character includes both halves of a surrogate pair。
您可以使用 StringInfo.GetTextElementEnumerator()
来做到这一点,将 string
分解成原子块,然后取第一个。
首先,定义如下扩展方法:
public static class TextExtensions
{
public static IEnumerable<string> TextElements(this string s)
{
// StringInfo.GetTextElementEnumerator is a .Net 1.1 class that doesn't implement IEnumerable<string>, so convert
if (s == null)
yield break;
var enumerator = StringInfo.GetTextElementEnumerator(s);
while (enumerator.MoveNext())
yield return enumerator.GetTextElement();
}
}
现在,您可以:
var result2 = highUnicodeChar.TextElements().FirstOrDefault() ?? "";
请注意,StringInfo.GetTextElementEnumerator()
还将对 Unicode combining characters 进行分组,因此字符串 Ĥ=T̂+V̂
的第一个字素簇将是 Ĥ
而不是 H
。
示例 fiddle here.
我正在尝试将一个高位 Unicode 字符从一个字符串读入另一个字符串。为了简洁起见,我将简化我的代码,如下所示:
public static void UnicodeTest()
{
var highUnicodeChar = ""; //Not the standard A
var result1 = highUnicodeChar; //this works
var result2 = highUnicodeChar[0].ToString(); // returns \ud835
}
当我直接将highUnicodeChar
赋给result1
时,它保留了</code>的字面值。当我尝试通过索引访问它时,它 returns <code>\ud835
。据我了解,这是一对用于表示 UTF-32 字符的 UTF-16 字符代理项。我很确定这个问题与尝试将 char
隐式转换为 string
.
最后,我希望 result2
产生与 result1
相同的值。我该怎么做?
在 Unicode, you have code points. These are 21 bits long. Your character , Mathematical Bold Capital A
中,代码点为 U+1D400。
在 Unicode 编码中,您有 代码单元 。这些是编码的自然单位:8-bit for UTF-8, 16-bit for UTF-16,等等。一个或多个代码单元编码单个代码点。
在 UTF-16 中,形成单个代码点的两个代码单元称为 代理对。代理对用于编码大于 16 位的任何代码点,即 U+10000 及以上。
这在 .NET 中有点棘手,因为 .NET Char
表示单个 UTF-16 代码单元,而 .NET String
是代码单元的集合。
因此您的代码点 (U+1D400) 不能容纳在 16 位中并且需要一个代理项对,这意味着您的字符串中有两个代码单元:
var highUnicodeChar = "";
char a = highUnicodeChar[0]; // code unit 0xD835
char b = highUnicodeChar[1]; // code unit 0xDC00
这意味着当您像这样索引字符串时,您实际上只得到代理项对的一半。
您可以使用 IsSurrogatePair 来测试代理对。例如:
string GetFullCodePointAtIndex(string s, int idx) =>
s.Substring(idx, char.IsSurrogatePair(s, idx) ? 2 : 1);
重要的是要注意 Unicode 中变量编码的兔子洞并没有在代码点结束。 字素簇 是 "visible thing" 大多数人在被问到时最终会称其为 "character"。一个字素簇由一个或多个代码点组成:一个基本字符和零个或多个组合字符。组合字符的一个示例是变音符号或您可能想要添加的其他各种 decorations/modifiers。请参阅 this answer 了解组合字符的可怕示例。
要测试组合字符,您可以使用 GetUnicodeCategory 检查封闭标记、非间距标记或间距标记。
您似乎想从用户的角度提取第一个 "atomic" 字符(即第一个 Unicode grapheme cluster) from the highUnicodeChar
string, where an "atomic" character includes both halves of a surrogate pair。
您可以使用 StringInfo.GetTextElementEnumerator()
来做到这一点,将 string
分解成原子块,然后取第一个。
首先,定义如下扩展方法:
public static class TextExtensions
{
public static IEnumerable<string> TextElements(this string s)
{
// StringInfo.GetTextElementEnumerator is a .Net 1.1 class that doesn't implement IEnumerable<string>, so convert
if (s == null)
yield break;
var enumerator = StringInfo.GetTextElementEnumerator(s);
while (enumerator.MoveNext())
yield return enumerator.GetTextElement();
}
}
现在,您可以:
var result2 = highUnicodeChar.TextElements().FirstOrDefault() ?? "";
请注意,StringInfo.GetTextElementEnumerator()
还将对 Unicode combining characters 进行分组,因此字符串 Ĥ=T̂+V̂
的第一个字素簇将是 Ĥ
而不是 H
。
示例 fiddle here.