Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье приводятся дополнительные замечания к справочной документации по этому 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 являются суррогатными точками кода. Ни одна суррогатная кодовая точка не имеет достаточно информации, чтобы определить, является ли она буквой.
Если изменить этот код, используя 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
Код, который специально обрабатывает суррогатные пары
Рекомендуется использовать тип, 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.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;
}
Существуют некоторые различия между 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
. Чтобы узнать количество экземпляров char
, которое будет результатом преобразования экземпляра Rune
в 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.EncodeToUtf16 и Rune.EncodeToUtf8 возвращают фактическое количество записанных элементов. Вызывается исключение, если целевой буфер слишком короток, чтобы уместить результат. Существуют невыбрасывающие TryEncodeToUtf8 и TryEncodeToUtf16 методы для вызывающих функций, которые хотят избежать выброса исключений.
Rune в .NET и других языках
Термин "руна" не определен в стандарте Юникода. Термин восходит к созданию UTF-8. Роб Пайк и Кен Томпсон искали термин, чтобы описать, что в конечном итоге станет известной как кодовая точка. Они остановились на термине "rune", и позднее влияние Роба Пайка на язык программирования Go помогло популяризировать этот термин.
Однако тип .NET Rune
не эквивалентен типу Go rune
. В Go тип rune
является псевдонимом для int32
. Го руна предназначена для представления кодовой точки Юникода, но руна может представлять любое 32-разрядное значение, включая суррогатные кодовые точки и значения, которые не являются допустимыми кодовыми точками Юникода.
Аналогичные типы на других языках программирования см. в разделе "Примитивный char
тип Rust" или "SwiftUnicode.Scalar
", оба из которых представляют скалярные значения Юникода. Они предоставляют функциональные возможности, аналогичные типу Rune
платформы .NET, и запрещают инициализацию значений, которые не являются законными скалярными значениями Юникода.