System.Text.Rune struct
Este artigo fornece observações complementares à documentação de referência para essa API.
Uma Rune instância representa um valor escalar Unicode, o que significa qualquer ponto de código excluindo o intervalo substituto (U+D800.. U+DFFF). Os construtores e operadores de conversão do tipo validam a entrada, para que os consumidores possam chamar as APIs supondo que a instância subjacente Rune esteja bem formada.
Se você não estiver familiarizado com os termos valor escalar Unicode, ponto de código, intervalo substituto e bem formado, consulte Introdução à codificação de caracteres no .NET.
Quando usar o tipo Runa
Considere usar o tipo se o Rune
seu código:
- Chama APIs que exigem valores escalares Unicode
- Manipula explicitamente pares substitutos
APIs que exigem valores escalares Unicode
Se seu código iterar pelas char
instâncias em um string
ou um ReadOnlySpan<char>
, alguns dos char
métodos não funcionarão corretamente em char
instâncias que estão no intervalo substituto. Por exemplo, as seguintes APIs exigem um valor char
escalar para funcionar corretamente:
- Char.GetNumericValue
- Char.GetUnicodeCategory
- Char.IsDigit
- Char.IsLetter
- Char.IsLetterOrDigit
- Char.IsLower
- Char.IsNumber
- Char.IsPunctuation
- Char.IsSymbol
- Char.IsUpper
O exemplo a seguir mostra o código que não funcionará corretamente se qualquer uma das instâncias for pontos de código substitutos 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
Aqui está o código equivalente que funciona com um 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;
}
O código anterior funciona corretamente com alguns idiomas, como o inglês:
CountLettersInString("Hello")
// Returns 5
Mas não funcionará corretamente para idiomas fora do Plano Multilíngue Básico, como o Osage:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 0
A razão pela qual esse método retorna resultados incorretos para texto Osage é que as instâncias para letras Osage são pontos de código substitutos char
. Nenhum ponto de código substituto tem informações suficientes para determinar se é uma letra.
Se você alterar esse código para usar Rune
em vez de , o método funciona corretamente com pontos de char
código fora do plano multilíngue básico:
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
Aqui está o código equivalente que funciona com um ReadOnlySpan<char>
:
static int CountLetters(ReadOnlySpan<char> span)
{
int letterCount = 0;
foreach (Rune rune in span.EnumerateRunes())
{
if (Rune.IsLetter(rune))
{ letterCount++; }
}
return letterCount;
}
O código anterior conta as letras Osage corretamente:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 8
Código que manipula explicitamente pares substitutos
Considere usar o Rune
tipo se seu código chamar APIs que operam explicitamente em pontos de código substitutos, como os seguintes métodos:
- Char.IsSurrogate
- Char.IsSurrogatePair
- Char.IsHighSurrogate
- Char.IsLowSurrogate
- Char.ConvertFromUtf32
- Char.ConvertToUtf32
Por exemplo, o método a seguir tem lógica especial para lidar com pares substitutos 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.");
}
}
}
Esse código é mais simples se ele usa Rune
, como no exemplo a seguir:
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
}
}
Quando não usar Rune
Você não precisa usar o Rune
tipo se seu código:
- Procura correspondências exatas
char
- Divide uma cadeia de caracteres em um valor de caractere conhecido
Usar o Rune
tipo pode retornar resultados incorretos se seu código:
- Conta o número de caracteres de exibição em um
string
Procure correspondências exatas char
O código a seguir itera através de uma string
procura por caracteres específicos, retornando o índice da primeira correspondência. Não há necessidade de alterar esse código para usar Rune
, pois o código está procurando caracteres que são representados por um único 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
}
Dividir uma cadeia de caracteres em um conhecido char
É comum chamar string.Split
e usar delimitadores como ' '
(espaço) ou ','
(vírgula), como no exemplo a seguir:
string inputString = "🐂, 🐄, 🐆";
string[] splitOnSpace = inputString.Split(' ');
string[] splitOnComma = inputString.Split(',');
Não há necessidade de usar Rune
aqui, porque o código está procurando caracteres que são representados por um único char
arquivo .
Contar o número de caracteres de exibição em um string
O número de ocorrências em uma cadeia de caracteres pode não corresponder ao número de caracteres perceptíveis pelo usuário mostrados ao exibir a cadeia de Rune
caracteres.
Como Rune
as instâncias representam valores escalares Unicode, os componentes que seguem as diretrizes de segmentação de texto Unicode podem ser usados Rune
como um bloco de construção para contar caracteres de exibição.
O StringInfo tipo pode ser usado para contar caracteres de exibição, mas não conta corretamente em todos os cenários para implementações do .NET diferentes do .NET 5+.
Para obter mais informações, consulte Clusters de grafema.
Como instanciar um Rune
Há várias maneiras de obter uma Rune
instância. Você pode usar um construtor para criar um Rune
diretamente de:
Um ponto de código.
Rune a = new Rune(0x0061); // LATIN SMALL LETTER A Rune b = new Rune(0x10421); // DESERET CAPITAL LETTER ER
Um único
char
.Rune c = new Rune('a');
Um par substituto
char
.Rune d = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
Todos os construtores lançam um se a entrada não representa um ArgumentException
valor escalar Unicode válido.
Há Rune.TryCreate métodos disponíveis para chamadores que não querem que exceções sejam lançadas em caso de falha.
Rune
As instâncias também podem ser lidas a partir de sequências de entrada existentes. Por exemplo, dado um que representa dados ReadOnlySpan<char>
UTF-16, o Rune.DecodeFromUtf16 método retorna a primeira Rune
instância no início da extensão de entrada. O Rune.DecodeFromUtf8 método opera de forma semelhante, aceitando um ReadOnlySpan<byte>
parâmetro que representa dados UTF-8. Existem métodos equivalentes para ler a partir do final do span em vez do início do span.
Propriedades de consulta de um Rune
Para obter o valor de ponto de código inteiro de uma Rune
instância, use a Rune.Value propriedade.
Rune rune = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
int codePoint = rune.Value; // = 128302 decimal (= 0x1F52E)
Muitas das APIs estáticas disponíveis no char
tipo também estão disponíveis no Rune
tipo. Por exemplo, Rune.IsWhiteSpace e são equivalentes a Char.IsWhiteSpace e Rune.GetUnicodeCategoryChar.GetUnicodeCategory métodos. Os Rune
métodos manipulam corretamente os pares substitutos.
O código de exemplo a seguir usa uma entrada como e corta do início e do final da extensão, tudo Rune
o que não é uma ReadOnlySpan<char>
letra ou um dígito.
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;
}
Existem algumas diferenças de API entre char
e Rune
. Por exemplo:
- Não
Rune
há equivalente a Char.IsSurrogate(Char), uma vez queRune
as instâncias por definição nunca podem ser pontos de código substitutos. - O Rune.GetUnicodeCategory nem sempre retorna o mesmo resultado que Char.GetUnicodeCategoryo . Ele retorna o mesmo valor que CharUnicodeInfo.GetUnicodeCategory. Para obter mais informações, consulte os Comentários sobre Char.GetUnicodeCategory.
Converter a Rune
para UTF-8 ou UTF-16
Como a Rune
é um valor escalar Unicode, ele pode ser convertido em codificação UTF-8, UTF-16 ou UTF-32. O Rune
tipo tem suporte interno para conversão para UTF-8 e UTF-16.
O Rune.EncodeToUtf16 converte uma Rune
instância em char
instâncias. Para consultar o número de instâncias que resultariam da conversão de char
uma Rune
instância em UTF-16, use a Rune.Utf16SequenceLength propriedade. Existem métodos semelhantes para a conversão UTF-8.
O exemplo a seguir converte uma instância em uma Rune
char
matriz. O código pressupõe que você tenha uma Rune
instância na rune
variável:
char[] chars = new char[rune.Utf16SequenceLength];
int numCharsWritten = rune.EncodeToUtf16(chars);
Como a é uma sequência de caracteres UTF-16, o exemplo a string
seguir também converte uma Rune
instância em UTF-16:
string theString = rune.ToString();
O exemplo a seguir converte uma instância em uma Rune
UTF-8
matriz de bytes:
byte[] bytes = new byte[rune.Utf8SequenceLength];
int numBytesWritten = rune.EncodeToUtf8(bytes);
Os Rune.EncodeToUtf16 métodos e Rune.EncodeToUtf8 retornam o número real de elementos gravados. Eles lançam uma exceção se o buffer de destino for muito curto para conter o resultado. Existem métodos de não-lançamento TryEncodeToUtf8 e TryEncodeToUtf16 também para os chamadores que querem evitar exceções.
Runa no .NET versus outras linguagens
O termo "runa" não é definido no padrão Unicode. O termo remonta à criação do UTF-8. Rob Pike e Ken Thompson estavam procurando um termo para descrever o que viria a ser conhecido como um ponto de código. Eles estabeleceram o termo "runa", e a influência posterior de Rob Pike sobre a linguagem de programação Go ajudou a popularizar o termo.
No entanto, o tipo .NET Rune
não é o equivalente do tipo Go rune
. Em Go, o rune
tipo é um alias para int32
. Uma runa Go destina-se a representar um ponto de código Unicode, mas pode ser qualquer valor de 32 bits, incluindo pontos de código substitutos e valores que não são pontos de código Unicode legais.
Para tipos semelhantes em outras linguagens de programação, consulte o tipo primitivo char
de Rust ou o tipo de Unicode.Scalar
Swift, que representam valores escalares Unicode. Eles fornecem funcionalidade semelhante ao . Rune
NET e eles não permitem a instanciação de valores que não são valores escalares Unicode legais.