Struct System.Text.Rune
Questo articolo fornisce osservazioni supplementari alla documentazione di riferimento per questa API.
Un'istanza Rune rappresenta un valore scalare Unicode, ovvero qualsiasi punto di codice escluso l'intervallo surrogato (U+D800.. U+DFFF). I costruttori e gli operatori di conversione del tipo convalidano l'input, in modo che i consumer possano chiamare le API presupponendo che l'istanza sottostante Rune sia ben formata.
Se non si ha familiarità con i termini valore scalare Unicode, punto di codice, intervallo surrogato e formato corretto, vedere Introduzione alla codifica dei caratteri in .NET.
Quando usare il tipo Rune
Prendere in considerazione l'uso del Rune
tipo se il codice:
- Chiama le API che richiedono valori scalari Unicode
- Gestisce in modo esplicito le coppie di surrogati
API che richiedono valori scalari Unicode
Se il codice scorre le char
istanze in un string
oggetto o , ReadOnlySpan<char>
alcuni dei char
metodi non funzioneranno correttamente nelle char
istanze incluse nell'intervallo di surrogati. Ad esempio, le API seguenti richiedono un valore char
scalare per funzionare correttamente:
- Char.GetNumericValue
- Char.GetUnicodeCategory
- Char.IsDigit
- Char.IsLetter
- Char.IsLetterOrDigit
- Char.IsLower
- Char.IsNumber
- Char.IsPunctuation
- Char.IsSymbol
- Char.IsUpper
L'esempio seguente mostra il codice che non funzionerà correttamente se una delle char
istanze è costituita da punti di codice surrogati:
// 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
Ecco il codice equivalente che funziona con un oggetto 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;
}
Il codice precedente funziona correttamente con alcune lingue, ad esempio inglese:
CountLettersInString("Hello")
// Returns 5
Ma non funziona correttamente per le lingue esterne al piano multilingue di base, ad esempio Osage:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 0
Il motivo per cui questo metodo restituisce risultati non corretti per il testo Osage è che le char
istanze per le lettere osage sono punti di codice surrogato. Nessun singolo punto di codice surrogato ha informazioni sufficienti per determinare se si tratta di una lettera.
Se si modifica questo codice in modo che venga usato Rune
invece di char
, il metodo funziona correttamente con i punti di codice all'esterno del piano multilingue basic:
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
Ecco il codice equivalente che funziona con un oggetto ReadOnlySpan<char>
:
static int CountLetters(ReadOnlySpan<char> span)
{
int letterCount = 0;
foreach (Rune rune in span.EnumerateRunes())
{
if (Rune.IsLetter(rune))
{ letterCount++; }
}
return letterCount;
}
Il codice precedente conta correttamente le lettere osage:
CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 8
Codice che gestisce in modo esplicito le coppie di surrogati
Prendere in considerazione l'uso del tipo se il Rune
codice chiama le API che operano in modo esplicito sui punti di codice surrogato, ad esempio i metodi seguenti:
- Char.IsSurrogate
- Char.IsSurrogatePair
- Char.IsHighSurrogate
- Char.IsLowSurrogate
- Char.ConvertFromUtf32
- Char.ConvertToUtf32
Ad esempio, il metodo seguente ha una logica speciale per gestire le coppie di surrogati 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.");
}
}
}
Questo codice è più semplice se usa Rune
, come nell'esempio seguente:
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 evitare l'uso di Rune
Non è necessario usare il Rune
tipo se il codice:
- Cerca corrispondenze esatte
char
- Divide una stringa su un valore char noto
L'uso del Rune
tipo può restituire risultati non corretti se il codice:
- Conta il numero di caratteri visualizzati in un
string
Cercare corrispondenze esatte char
Il codice seguente esegue l'iterazione di una string
ricerca di caratteri specifici, restituendo l'indice della prima corrispondenza. Non è necessario modificare questo codice per usare Rune
, perché il codice cerca caratteri rappresentati da un singolo char
oggetto .
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
}
Dividere una stringa in un oggetto noto char
È comune chiamare string.Split
e usare delimitatori come ' '
(spazio) o ','
(virgola), come nell'esempio seguente:
string inputString = "🐂, 🐄, 🐆";
string[] splitOnSpace = inputString.Split(' ');
string[] splitOnComma = inputString.Split(',');
Non è necessario usare Rune
qui, perché il codice cerca caratteri rappresentati da un singolo char
oggetto .
Contare il numero di caratteri visualizzati in un string
Il numero di Rune
istanze in una stringa potrebbe non corrispondere al numero di caratteri percepibili dell'utente visualizzati durante la visualizzazione della stringa.
Poiché Rune
le istanze rappresentano valori scalari Unicode, i componenti che seguono le linee guida per la segmentazione di testo Unicode possono essere usati Rune
come blocco predefinito per contare i caratteri di visualizzazione.
Il StringInfo tipo può essere usato per contare i caratteri di visualizzazione, ma non viene conteggiato correttamente in tutti gli scenari per le implementazioni di .NET diverse da .NET 5+.
Per altre informazioni, vedere Cluster Grapheme.
Come creare un'istanza di Rune
Esistono diversi modi per ottenere un'istanza Rune
. È possibile usare un costruttore per creare un oggetto Rune
direttamente da:
Punto di codice.
Rune a = new Rune(0x0061); // LATIN SMALL LETTER A Rune b = new Rune(0x10421); // DESERET CAPITAL LETTER ER
char
Singolo oggetto .Rune c = new Rune('a');
Coppia di surrogati
char
.Rune d = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
Tutti i costruttori generano un'eccezione ArgumentException
se l'input non rappresenta un valore scalare Unicode valido.
Sono Rune.TryCreate disponibili metodi per i chiamanti che non vogliono che le eccezioni vengano generate in caso di errore.
Rune
Le istanze possono anche essere lette da sequenze di input esistenti. Ad esempio, dato che rappresenta ReadOnlySpan<char>
i dati UTF-16, il Rune.DecodeFromUtf16 metodo restituisce la prima Rune
istanza all'inizio dell'intervallo di input. Il Rune.DecodeFromUtf8 metodo funziona in modo analogo, accettando un ReadOnlySpan<byte>
parametro che rappresenta i dati UTF-8. Esistono metodi equivalenti da leggere dalla fine dell'intervallo anziché dall'inizio dell'intervallo.
Proprietà di query di un oggetto Rune
Per ottenere il valore del punto di codice intero di un'istanza Rune
di , utilizzare la Rune.Value proprietà .
Rune rune = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
int codePoint = rune.Value; // = 128302 decimal (= 0x1F52E)
Molte delle API statiche disponibili nel char
tipo sono disponibili anche nel Rune
tipo . Ad esempio, Rune.IsWhiteSpace e sono equivalenti ai Char.IsWhiteSpace metodi e Rune.GetUnicodeCategoryChar.GetUnicodeCategory . I Rune
metodi gestiscono correttamente le coppie di surrogati.
Il codice di esempio seguente accetta come ReadOnlySpan<char>
input e taglia sia dall'inizio che dalla fine dell'intervallo ogni Rune
che non è una lettera o una cifra.
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;
}
Esistono alcune differenze api tra char
e Rune
. Ad esempio:
- Non esiste alcun
Rune
equivalente a Char.IsSurrogate(Char), poichéRune
le istanze per definizione non possono mai essere punti di codice surrogato. - Non Rune.GetUnicodeCategory restituisce sempre lo stesso risultato di Char.GetUnicodeCategory. Restituisce lo stesso valore di CharUnicodeInfo.GetUnicodeCategory. Per altre informazioni, vedere la sezione Osservazioni su Char.GetUnicodeCategory.
Convertire un oggetto Rune
in UTF-8 o UTF-16
Poiché è Rune
un valore scalare Unicode, può essere convertito in codifica UTF-8, UTF-16 o UTF-32. Il Rune
tipo include il supporto predefinito per la conversione in UTF-8 e UTF-16.
Converte un'istanza Rune.EncodeToUtf16Rune
in char
istanze di . Per eseguire una query sul numero di istanze risultanti dalla conversione di char
un'istanza Rune
in UTF-16, usare la Rune.Utf16SequenceLength proprietà . Esistono metodi simili per la conversione UTF-8.
Nell'esempio seguente viene convertita un'istanza Rune
in una char
matrice. Il codice presuppone che nella variabile sia presente un'istanza Rune
rune
:
char[] chars = new char[rune.Utf16SequenceLength];
int numCharsWritten = rune.EncodeToUtf16(chars);
Poiché è string
una sequenza di caratteri UTF-16, l'esempio seguente converte anche un'istanza Rune
in UTF-16:
string theString = rune.ToString();
L'esempio seguente converte un'istanza Rune
in una UTF-8
matrice di byte:
byte[] bytes = new byte[rune.Utf8SequenceLength];
int numBytesWritten = rune.EncodeToUtf8(bytes);
I Rune.EncodeToUtf16 metodi e Rune.EncodeToUtf8 restituiscono il numero effettivo di elementi scritti. Generano un'eccezione se il buffer di destinazione è troppo breve per contenere il risultato. Per i chiamanti che vogliono evitare eccezioni, sono disponibili metodi e TryEncodeToUtf16 non generabiliTryEncodeToUtf8.
Esecuzione in .NET e altri linguaggi
Il termine "rune" non è definito nello standard Unicode. Il termine risale alla creazione di UTF-8. Rob Pike e Ken Thompson cercavano un termine per descrivere ciò che alla fine sarebbe diventato noto come punto di codice. Si stabilirono sul termine "rune" e l'influenza successiva di Rob Pike sul linguaggio di programmazione Go aiutarono a popolare il termine.
Tuttavia, il tipo .NET Rune
non è l'equivalente del tipo Go rune
. In Go il rune
tipo è un alias per int32
. Un runa Go è progettato per rappresentare un punto di codice Unicode, ma può essere qualsiasi valore a 32 bit, inclusi i punti di codice surrogato e i valori che non sono punti di codice Unicode legali.
Per tipi simili in altri linguaggi di programmazione, vedere Il tipo primitivo char
o swift di Unicode.Scalar
Rust, entrambi i quali rappresentano valori scalari Unicode. Forniscono funzionalità simili a . Il tipo di Rune
NET e non consentono la creazione di istanze di valori che non sono valori scalari Unicode legali.