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:

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:

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 charoggetto .

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 charoggetto .

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
    
  • charSingolo 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:

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 Runerune :

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.