System.Text.Rune 结构
本文提供了此 API 参考文档的补充说明。
Rune实例表示 Unicode 标量值,这意味着排除代理项范围(U+D800.)的任何代码点。U+DFFF)。 该类型的构造函数和转换运算符验证输入,因此使用者可以调用 API(假设基础 Rune 实例格式良好)。
如果不熟悉 Unicode 标量值、代码点、代理项范围和格式正确的术语,请参阅 .NET 中的字符编码简介。
何时使用 Rune 类型
如果代码有以下几点, Rune
请考虑使用类型:
- 调用需要 Unicode 标量值的 API
- 显式处理代理项对
需要 Unicode 标量值的 API
如果代码循环访问char
某个或 a ReadOnlySpan<char>
中的实例,则某些char
方法在代理项范围内的实例上无法正常工作char
string
。 例如,以下 API 需要标量值 char
才能正常工作:
- Char.GetNumericValue
- Char.GetUnicodeCategory
- Char.IsDigit
- Char.IsLetter
- Char.IsLetterOrDigit
- Char.IsLower
- Char.IsNumber
- Char.IsPunctuation
- Char.IsSymbol
- Char.IsUpper
以下示例演示了如果任一 char
实例是代理项代码点,则无法正常工作的代码:
// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
int CountLettersBadExample(string s)
{
int letterCount = 0;
foreach (char ch in s)
{
if (char.IsLetter(ch))
{ letterCount++; }
}
return letterCount;
}
// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
let countLettersBadExample (s: string) =
let mutable letterCount = 0
for ch in s do
if Char.IsLetter ch then
letterCount <- letterCount + 1
letterCount
下面是适用于 ReadOnlySpan<char>
以下代码的等效代码:
// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
static int CountLettersBadExample(ReadOnlySpan<char> span)
{
int letterCount = 0;
foreach (char ch in span)
{
if (char.IsLetter(ch))
{ letterCount++; }
}
return letterCount;
}
上述代码适用于某些语言(如英语):
CountLettersInString("Hello")
// Returns 5
但是,它不适用于基本多语言平面之外的语言,例如 Osage:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 0
此方法返回 Osage 文本的错误 char
结果的原因是 Osage 字母的实例是代理代码点。 没有单个代理项代码点有足够的信息来确定它是否为字母。
如果将此代码更改为使用 Rune
,而不是 char
使用此方法,该方法适用于基本多语言平面之外的代码点:
int CountLetters(string s)
{
int letterCount = 0;
foreach (Rune rune in s.EnumerateRunes())
{
if (Rune.IsLetter(rune))
{ letterCount++; }
}
return letterCount;
}
let countLetters (s: string) =
let mutable letterCount = 0
for rune in s.EnumerateRunes() do
if Rune.IsLetter rune then
letterCount <- letterCount + 1
letterCount
下面是适用于 ReadOnlySpan<char>
以下代码的等效代码:
static int CountLetters(ReadOnlySpan<char> span)
{
int letterCount = 0;
foreach (Rune rune in span.EnumerateRunes())
{
if (Rune.IsLetter(rune))
{ letterCount++; }
}
return letterCount;
}
前面的代码正确计算 Osage 字母:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 8
显式处理代理项对的代码
如果代码调用对代理项代码点显式操作的 API,请考虑使用 Rune
类型,例如以下方法:
- Char.IsSurrogate
- Char.IsSurrogatePair
- Char.IsHighSurrogate
- Char.IsLowSurrogate
- Char.ConvertFromUtf32
- Char.ConvertToUtf32
例如,以下方法具有处理代理项 char
对的特殊逻辑:
static void ProcessStringUseChar(string s)
{
Console.WriteLine("Using char");
for (int i = 0; i < s.Length; i++)
{
if (!char.IsSurrogate(s[i]))
{
Console.WriteLine($"Code point: {(int)(s[i])}");
}
else if (i + 1 < s.Length && char.IsSurrogatePair(s[i], s[i + 1]))
{
int codePoint = char.ConvertToUtf32(s[i], s[i + 1]);
Console.WriteLine($"Code point: {codePoint}");
i++; // so that when the loop iterates it's actually +2
}
else
{
throw new Exception("String was not well-formed UTF-16.");
}
}
}
如果此类代码使用 Rune
,则更简单,如以下示例所示:
static void ProcessStringUseRune(string s)
{
Console.WriteLine("Using Rune");
for (int i = 0; i < s.Length;)
{
if (!Rune.TryGetRuneAt(s, i, out Rune rune))
{
throw new Exception("String was not well-formed UTF-16.");
}
Console.WriteLine($"Code point: {rune.Value}");
i += rune.Utf16SequenceLength; // increment the iterator by the number of chars in this Rune
}
}
何时不使用 Rune
如果代码如下所示,则无需使用该 Rune
类型:
- 查找完全
char
匹配项 - 在已知字符值上拆分字符串
Rune
如果代码如下所示,使用类型可能会返回不正确的结果:
- 对显示字符数进行计数
string
查找完全 char
匹配项
以下代码循环访问 string
查找特定字符,并返回第一个匹配项的索引。 无需更改此代码即可使用 Rune
,因为代码查找由单个 char
表示的字符。
int GetIndexOfFirstAToZ(string s)
{
for (int i = 0; i < s.Length; i++)
{
char thisChar = s[i];
if ('A' <= thisChar && thisChar <= 'Z')
{
return i; // found a match
}
}
return -1; // didn't find 'A' - 'Z' in the input string
}
拆分已知字符串 char
通常调用 string.Split
和使用分隔符(如 ' '
(空格)或 ','
(逗号),如以下示例所示:
string inputString = "🐂, 🐄, 🐆";
string[] splitOnSpace = inputString.Split(' ');
string[] splitOnComma = inputString.Split(',');
无需在此处使用 Rune
,因为代码查找由单个 char
表示的字符。
对显示字符数进行计数 string
字符串中的实例数 Rune
可能与显示字符串时显示的用户感知字符数不匹配。
由于 Rune
实例表示 Unicode 标量值,因此遵循 Unicode 文本分段准则的 组件可以用作 Rune
计算显示字符的构建基块。
该 StringInfo 类型可用于对显示字符进行计数,但在除 .NET 5+ 以外的 .NET 实现的所有方案中,该类型不会正确计数。
有关详细信息,请参阅 Grapheme 群集。
如何实例化 Rune
可通过多种方式获取 Rune
实例。 可以使用构造函数直接从以下项创建 Rune
:
代码点。
Rune a = new Rune(0x0061); // LATIN SMALL LETTER A Rune b = new Rune(0x10421); // DESERET CAPITAL LETTER ER
单个
char
。Rune c = new Rune('a');
代理项
char
对。Rune d = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
如果输入不表示有效的 Unicode 标量值,则所有构造函数都会引发 ArgumentException
。
有 Rune.TryCreate 一些方法可用于不希望在失败时引发异常的调用方。
Rune
实例也可以从现有输入序列中读取。 例如,给定一个 ReadOnlySpan<char>
表示 UTF-16 数据的方法返回 Rune.DecodeFromUtf16 输入范围开头的第一个 Rune
实例。 该方法 Rune.DecodeFromUtf8 同样运行,接受 ReadOnlySpan<byte>
表示 UTF-8 数据的参数。 有等效的方法可从范围末尾读取,而不是范围开头。
查询 a Rune
若要获取实例的 Rune
整数代码点值,请使用 Rune.Value 该属性。
Rune rune = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
int codePoint = rune.Value; // = 128302 decimal (= 0x1F52E)
该类型上 char
提供的许多静态 API 也可用于该 Rune
类型。 例如, Rune.IsWhiteSpace 等效 Rune.GetUnicodeCategory 于 Char.IsWhiteSpace 和 Char.GetUnicodeCategory 方法。 方法 Rune
正确处理代理项对。
下面的示例代码采用一个 ReadOnlySpan<char>
输入和剪裁,从范围开始和结尾,每个 Rune
范围不是字母或数字。
static ReadOnlySpan<char> TrimNonLettersAndNonDigits(ReadOnlySpan<char> span)
{
// First, trim from the front.
// If any Rune can't be decoded
// (return value is anything other than "Done"),
// or if the Rune is a letter or digit,
// stop trimming from the front and
// instead work from the end.
while (Rune.DecodeFromUtf16(span, out Rune rune, out int charsConsumed) == OperationStatus.Done)
{
if (Rune.IsLetterOrDigit(rune))
{ break; }
span = span[charsConsumed..];
}
// Next, trim from the end.
// If any Rune can't be decoded,
// or if the Rune is a letter or digit,
// break from the loop, and we're finished.
while (Rune.DecodeLastFromUtf16(span, out Rune rune, out int charsConsumed) == OperationStatus.Done)
{
if (Rune.IsLetterOrDigit(rune))
{ break; }
span = span[..^charsConsumed];
}
return span;
}
之间存在一些 API 差异char
。Rune
例如:
- 没有
Rune
等效项 Char.IsSurrogate(Char),因为Rune
根据定义,实例永远不能是代理码位。 - 这 Rune.GetUnicodeCategory 并不总是返回与 Char.GetUnicodeCategory. 它确实返回与 ..CharUnicodeInfo.GetUnicodeCategory 有关详细信息,请参阅“备注”。Char.GetUnicodeCategory
Rune
转换为 UTF-8 或 UTF-16
由于 a Rune
是 Unicode 标量值,因此可以转换为 UTF-8、UTF-16 或 UTF-32 编码。 该 Rune
类型具有对转换为 UTF-8 和 UTF-16 的内置支持。
将Rune.EncodeToUtf16实例转换为Rune
char
实例。 若要查询将实例转换为 Rune
UTF-16 而生成的实例数char
,请使用该Rune.Utf16SequenceLength属性。 UTF-8 转换存在类似的方法。
以下示例将 Rune
实例转换为 char
数组。 代码假定变量中有rune
一个Rune
实例:
char[] chars = new char[rune.Utf16SequenceLength];
int numCharsWritten = rune.EncodeToUtf16(chars);
由于 a string
是 UTF-16 字符序列,以下示例还会将 Rune
实例转换为 UTF-16:
string theString = rune.ToString();
以下示例将 Rune
实例转换为 UTF-8
字节数组:
byte[] bytes = new byte[rune.Utf8SequenceLength];
int numBytesWritten = rune.EncodeToUtf8(bytes);
和Rune.EncodeToUtf16Rune.EncodeToUtf8方法返回写入的实际元素数。 如果目标缓冲区太短而无法包含结果,则会引发异常。 对于想要避免异常的调用方,也存在非引发 TryEncodeToUtf8 和 TryEncodeToUtf16 方法。
.NET 中的 Rune 与其他语言
Unicode Standard 中未定义术语“rune”。 该术语可追溯到 UTF-8 的创建。 Rob Pike 和 Ken Thompson 正在寻找一个术语来描述最终被称为代码点的内容。 他们解决了术语“rune”,罗布·派克后来对 Go 编程语言的影响有助于普及这个词。
但是,.NET Rune
类型与 Go rune
类型不相等。 在 Go 中,类型 rune
是 别名 int32
。 Go rune 旨在表示 Unicode 码位,但它可以是任何 32 位值,包括代理代码点和不是合法 Unicode 码位的值。
有关其他编程语言中的类似类型,请参阅 Rust 的基元 char
类型 或 Swift Unicode.Scalar
的类型,这两种类型都表示 Unicode 标量值。 它们提供的功能类似于 .NET 的类型 Rune
,不允许实例化非合法 Unicode 标量值的值。