Структуру System.Text.Rune
В этой статье приводятся дополнительные замечания к справочной документации по этому API.
Rune Экземпляр представляет скалярное значение Юникода, что означает любую точку кода, за исключением суррогатного диапазона (U+D800.). U+DFFF). Конструкторы и операторы преобразования типа проверяют входные данные, поэтому потребители могут вызывать API, предполагая, что базовый Rune экземпляр хорошо сформирован.
Если вы не знакомы с терминами скалярное значение Юникода, точка кода, суррогатный диапазон и хорошо сформированный диапазон, см . статью "Введение в кодировку символов" в .NET.
Когда следует использовать тип Rune
Попробуйте использовать тип, Rune
если код:
- Вызывает API, требующие скалярных значений Юникода
- Явным образом обрабатывает суррогатные пары
API, требующие скалярных значений Юникода
Если код выполняет итерацию по char
экземплярам в string
или в, ReadOnlySpan<char>
некоторые char
из методов не будут работать правильно для char
экземпляров, которые находятся в суррогатном диапазоне. Например, для правильной работы следующих 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 являются суррогатными точками кода. Ни одна суррогатная кодовая точка не имеет достаточно информации, чтобы определить, является ли она буквой.
При изменении этого кода вместо этого char
метод работает правильно с точками кода Rune
за пределами базовой многоязычной плоскости:
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
Код, который явно обрабатывает суррогатные пары
Рекомендуется использовать тип, Rune
если код вызывает API, которые явно работают с суррогатными точками кода, такими как следующие методы:
- 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
совпадений - Разбивает строку на известное значение 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
экземпляры представляют скалярные значения Юникода, компоненты, которые следуют рекомендациям по сегментации текста Юникода, могут использовать Rune
в качестве стандартного блока для подсчета отображаемых символов.
Тип StringInfo можно использовать для подсчета отображаемых символов, но он не учитывается в всех сценариях для реализаций .NET, отличных от .NET 5+.
Дополнительные сведения см. в разделе "Кластеры 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
Все конструкторы вызывают исключение ArgumentException
, если входные данные не представляют допустимое скалярное значение Юникода.
Rune.TryCreate Существуют методы, доступные для вызывающих лиц, которые не хотят создавать исключения при сбое.
Rune
экземпляры также можно считывать из существующих входных последовательностей. Например, учитывая ReadOnlySpan<char>
, что представляет данные UTF-16, Rune.DecodeFromUtf16 метод возвращает первый Rune
экземпляр в начале входного диапазона. Метод Rune.DecodeFromUtf8 работает аналогично, принимая ReadOnlySpan<byte>
параметр, представляющий данные UTF-8. Существуют эквивалентные методы для чтения с конца диапазона вместо начала диапазона.
Свойства запроса объекта Rune
Чтобы получить целочисленное значение точки кода экземпляра Rune
, используйте Rune.Value свойство.
Rune rune = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
int codePoint = rune.Value; // = 128302 decimal (= 0x1F52E)
Многие статические API, доступные в типе char
, также доступны в типе Rune
. Например, Rune.IsWhiteSpace это Rune.GetUnicodeCategory эквиваленты и Char.GetUnicodeCategory методыChar.IsWhiteSpace. Методы 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;
}
Существуют некоторые различия между char
API и Rune
. Например:
Rune
Нет эквивалентаChar.IsSurrogate(Char), так какRune
экземпляры по определению никогда не могут быть суррогатными точками кода.- Не Rune.GetUnicodeCategory всегда возвращает тот же результат, что Char.GetUnicodeCategoryи . Он возвращает то же значение, что CharUnicodeInfo.GetUnicodeCategoryи . Дополнительные сведения см. в примечанияхChar.GetUnicodeCategory.
Преобразование в Rune
UTF-8 или UTF-16
Rune
Так как это скалярное значение Юникода, его можно преобразовать в кодировку UTF-8, UTF-16 или UTF-32. Тип Rune
имеет встроенную поддержку преобразования в UTF-8 и UTF-16.
Экземпляр Rune.EncodeToUtf16 преобразуется Rune
в char
экземпляры. Чтобы запросить количество экземпляров, которое приведет к преобразованию Rune
экземпляра char
в UTF-16, используйте Rune.Utf16SequenceLength это свойство. Аналогичные методы существуют для преобразования UTF-8.
В следующем примере экземпляр преобразуется Rune
в char
массив. В коде предполагается, что у вас есть Rune
экземпляр в переменной rune
:
char[] chars = new char[rune.Utf16SequenceLength];
int numCharsWritten = rune.EncodeToUtf16(chars);
Так как последовательность 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.EncodeToUtf8 Методы Rune.EncodeToUtf16 возвращают фактическое количество записываемых элементов. Они вызывают исключение, если целевой буфер слишком короткий, чтобы содержать результат. Существуют неисполнение TryEncodeToUtf8 и TryEncodeToUtf16 методы, а также для вызывающих лиц, которые хотят избежать исключений.
Rune в .NET и других языках
Термин "rune" не определен в Стандарте Юникода. Термин восходит к созданию UTF-8. Роб Пайк и Кен Томпсон искали термин, чтобы описать, что в конечном итоге станет известной как кодовая точка. Они поселились на термине "rune", и Роб Пайк позже влияние на язык программирования Go помогло популяризировать термин.
Однако тип .NET Rune
не эквивалентен типу Go rune
. В Go тип rune
является псевдонимом для int32
. Rune Go предназначен для представления кодовой точки Юникода, но это может быть любое 32-разрядное значение, включая суррогатные кодовые точки и значения, которые не являются законными кодовых точек Юникода.
Аналогичные типы на других языках программирования см. в разделе "Примитивный char
тип Rust" или "SwiftUnicode.Scalar
", оба из которых представляют скалярные значения Юникода. Они предоставляют функциональные возможности, аналогичные . Rune
Тип NET, и они запрещают создание экземпляров значений, которые не являются законными скалярными значениями Юникода.