Estructura System.Text.Rune
En este artículo se proporcionan comentarios adicionales a la documentación de referencia de esta API.
Una Rune instancia representa un valor escalar Unicode, lo que significa que cualquier punto de código excluyendo el intervalo suplente (U+D800.). U+DFFF). Los constructores y operadores de conversión del tipo validan la entrada, por lo que los consumidores pueden llamar a las API suponiendo que la instancia subyacente Rune esté bien formada.
Si no está familiarizado con los términos valor escalar Unicode, punto de código, intervalo suplente y correcto, consulte Introducción a la codificación de caracteres en .NET.
Cuándo usar el tipo rune
Considere la posibilidad de usar el Rune
tipo si el código:
- Llama a las API que requieren valores escalares Unicode.
- Controla explícitamente los pares suplentes
API que requieren valores escalares Unicode
Si el código recorre en iteración las char
instancias de o string
, ReadOnlySpan<char>
algunos de los char
métodos no funcionarán correctamente en char
instancias que se encuentran en el intervalo suplente. Por ejemplo, las SIGUIENTES API requieren que un valor char
escalar funcione correctamente:
- Char.GetNumericValue
- Char.GetUnicodeCategory
- Char.IsDigit
- Char.IsLetter
- Char.IsLetterOrDigit
- Char.IsLower
- Char.IsNumber
- Char.IsPunctuation
- Char.IsSymbol
- Char.IsUpper
En el ejemplo siguiente se muestra el código que no funcionará correctamente si alguna de las char
instancias son puntos de código suplentes:
// 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
Este es el código equivalente que funciona con :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;
}
El código anterior funciona correctamente con algunos idiomas como inglés:
CountLettersInString("Hello")
// Returns 5
Pero no funcionará correctamente para idiomas fuera del plano multilingüe básico, como Osage:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 0
La razón por la que este método devuelve resultados incorrectos para el texto de Osage es que las char
instancias de letras de Osage son puntos de código suplentes. Ningún punto de código suplente único tiene suficiente información para determinar si es una letra.
Si cambia este código para usar Rune
en lugar de , el método funciona correctamente con puntos de char
código fuera del plano multilingüe 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
Este es el código equivalente que funciona con :ReadOnlySpan<char>
static int CountLetters(ReadOnlySpan<char> span)
{
int letterCount = 0;
foreach (Rune rune in span.EnumerateRunes())
{
if (Rune.IsLetter(rune))
{ letterCount++; }
}
return letterCount;
}
El código anterior cuenta correctamente las letras de Osage:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 8
Código que controla explícitamente pares suplentes
Considere la posibilidad de usar el tipo si el Rune
código llama a las API que operan explícitamente en puntos de código suplentes, como los métodos siguientes:
- Char.IsSurrogate
- Char.IsSurrogatePair
- Char.IsHighSurrogate
- Char.IsLowSurrogate
- Char.ConvertFromUtf32
- Char.ConvertToUtf32
Por ejemplo, el método siguiente tiene lógica especial para tratar con pares suplentes 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.");
}
}
}
Este código es más sencillo si usa Rune
, como en el ejemplo siguiente:
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
}
}
Cuándo no debe usarse Rune
No es necesario usar el Rune
tipo si el código:
- Busca coincidencias exactas
char
- Divide una cadena en un valor char conocido.
El uso del Rune
tipo puede devolver resultados incorrectos si el código:
- Cuenta el número de caracteres para mostrar de un
string
Buscar coincidencias exactas char
El código siguiente recorre en iteración una string
búsqueda de caracteres específicos y devuelve el índice de la primera coincidencia. No es necesario cambiar este código para usar Rune
, ya que el código busca caracteres representados por un ú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 una cadena en un conocido char
Es habitual llamar string.Split
a y usar delimitadores como ' '
(espacio) o ','
(coma), como en el ejemplo siguiente:
string inputString = "🐂, 🐄, 🐆";
string[] splitOnSpace = inputString.Split(' ');
string[] splitOnComma = inputString.Split(',');
No es necesario usar Rune
aquí, ya que el código busca caracteres representados por un solo char
.
Recuento del número de caracteres para mostrar de un string
Es posible que el número de instancias de Rune
una cadena no coincida con el número de caracteres reconocibles por el usuario que se muestran al mostrar la cadena.
Dado Rune
que las instancias representan valores escalares Unicode, los componentes que siguen las instrucciones de segmentación de texto Unicode pueden usarse Rune
como bloque de creación para contar caracteres para mostrar.
El StringInfo tipo se puede usar para contar caracteres para mostrar, pero no cuenta correctamente en todos los escenarios de implementaciones de .NET que no sean .NET 5+.
Para más información, consulte Clústeres de Grapheme.
Creación de instancias de un Rune
Hay varias maneras de obtener una Rune
instancia. Puede usar un constructor para crear un Rune
directamente desde:
Un punto de código.
Rune a = new Rune(0x0061); // LATIN SMALL LETTER A Rune b = new Rune(0x10421); // DESERET CAPITAL LETTER ER
Un
char
único.Rune c = new Rune('a');
Un par suplente
char
.Rune d = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
Todos los constructores inician si ArgumentException
la entrada no representa un valor escalar Unicode válido.
Hay Rune.TryCreate métodos disponibles para los autores de llamadas que no quieren que se produzcan excepciones en caso de error.
Rune
Las instancias también se pueden leer de secuencias de entrada existentes. Por ejemplo, dado un ReadOnlySpan<char>
que representa datos UTF-16, el Rune.DecodeFromUtf16 método devuelve la primera Rune
instancia al principio del intervalo de entrada. El Rune.DecodeFromUtf8 método funciona de forma similar, aceptando un ReadOnlySpan<byte>
parámetro que representa datos UTF-8. Hay métodos equivalentes para leer desde el final del intervalo en lugar del principio del intervalo.
Propiedades de consulta de un Rune
Para obtener el valor de punto de código entero de una Rune
instancia, use la Rune.Value propiedad .
Rune rune = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
int codePoint = rune.Value; // = 128302 decimal (= 0x1F52E)
Muchas de las API estáticas disponibles en el char
tipo también están disponibles en el Rune
tipo . Por ejemplo, Rune.IsWhiteSpace y Rune.GetUnicodeCategory son equivalentes a Char.IsWhiteSpace los métodos y Char.GetUnicodeCategory . Los Rune
métodos controlan correctamente los pares suplentes.
El código de ejemplo siguiente toma como ReadOnlySpan<char>
entrada y recorta desde el principio y el final del intervalo cada Rune
uno que no es una letra o un 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;
}
Hay algunas diferencias de API entre char
y Rune
. Por ejemplo:
- No hay ningún
Rune
equivalente a Char.IsSurrogate(Char), ya queRune
las instancias por definición nunca pueden ser puntos de código suplentes. - No Rune.GetUnicodeCategory siempre devuelve el mismo resultado que Char.GetUnicodeCategory. Devuelve el mismo valor que CharUnicodeInfo.GetUnicodeCategory. Para obtener más información, vea comentarios sobre Char.GetUnicodeCategory.
Convertir a Rune
UTF-8 o UTF-16
Dado que es Rune
un valor escalar Unicode, se puede convertir en codificación UTF-8, UTF-16 o UTF-32. El Rune
tipo tiene compatibilidad integrada para la conversión a UTF-8 y UTF-16.
Rune.EncodeToUtf16 convierte una Rune
instancia en char
instancias. Para consultar el número de char
instancias resultantes de convertir una Rune
instancia en UTF-16, use la Rune.Utf16SequenceLength propiedad . Existen métodos similares para la conversión UTF-8.
En el ejemplo siguiente se convierte una Rune
instancia en una char
matriz. El código supone que tiene una Rune
instancia en la rune
variable :
char[] chars = new char[rune.Utf16SequenceLength];
int numCharsWritten = rune.EncodeToUtf16(chars);
Dado que es string
una secuencia de caracteres UTF-16, el ejemplo siguiente también convierte una Rune
instancia en UTF-16:
string theString = rune.ToString();
En el ejemplo siguiente se convierte una Rune
instancia en una matriz de UTF-8
bytes:
byte[] bytes = new byte[rune.Utf8SequenceLength];
int numBytesWritten = rune.EncodeToUtf8(bytes);
Los Rune.EncodeToUtf16 métodos y Rune.EncodeToUtf8 devuelven el número real de elementos escritos. Inician una excepción si el búfer de destino es demasiado corto para contener el resultado. Hay métodos y TryEncodeToUtf16 no iniciadoresTryEncodeToUtf8, así como para los autores de llamadas que desean evitar excepciones.
Ejecutar en .NET frente a otros lenguajes
El término "rune" no se define en el estándar Unicode. El término se remonta a la creación de UTF-8. Rob Pike y Ken Thompson buscaban un término para describir lo que finalmente se conocería como punto de código. Se establecieron en el término "rune", y la influencia posterior de Rob Pike sobre el lenguaje de programación Go ayudó a popularizar el término.
Sin embargo, el tipo de .NET Rune
no es el equivalente del tipo Go rune
. En Go, el rune
tipo es un alias para int32
. Un rune de Go está diseñado para representar un punto de código Unicode, pero puede ser cualquier valor de 32 bits, incluidos los puntos de código suplentes y los valores que no son puntos de código Unicode legales.
Para ver tipos similares en otros lenguajes de programación, consulte El tipo primitivo char
de Rust o el tipo de SwiftUnicode.Scalar
, ambos representan valores escalares Unicode. Proporcionan una funcionalidad similar a . El tipo de Rune
NET y no permiten la creación de instancias de valores que no son valores escalares Unicode legales.