System.Text.Rune-Struktur

Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.

Eine Rune Instanz stellt einen Unicode-Skalarwert dar, was bedeutet, dass ein beliebiger Codepunkt mit Ausnahme des Ersatzbereichs (U+D800.) steht. U+DFFF). Die Konstruktoren und Konvertierungsoperatoren des Typs überprüfen die Eingabe, sodass Verbraucher die APIs aufrufen können, vorausgesetzt, dass die zugrunde liegende Rune Instanz wohlgeformt ist.

Wenn Sie mit den Begriffen Unicode-Skalarwert, Codepunkt, Ersatzbereich und wohlgeformt nicht vertraut sind, lesen Sie die Einführung in die Zeichencodierung in .NET.

Wann der Rune-Typ verwendet werden soll

Erwägen Sie die Verwendung des Typs Rune , wenn Ihr Code:

  • Ruft APIs auf, die Unicode-Skalarwerte erfordern
  • Behandelt Surrogatepaare explizit

APIs, die Unicode-Skalarwerte erfordern

Wenn Ihr Code die char Instanzen in einer string oder einer ReadOnlySpan<char>Durchlaufen durchläuft, funktionieren einige der char Methoden nicht ordnungsgemäß für char Instanzen, die sich im Ersatzbereich befinden. Die folgenden APIs erfordern z. B. einen skalaren Wert char , um ordnungsgemäß zu funktionieren:

Das folgende Beispiel zeigt Code, der nicht ordnungsgemäß funktioniert, wenn eine der char Instanzen Surrogate-Codepunkte enthält:

// 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

Hier sehen Sie den entsprechenden Code, der mit einem 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;
}

Der vorangehende Code funktioniert ordnungsgemäß mit einigen Sprachen wie Englisch:

CountLettersInString("Hello")
// Returns 5

Es funktioniert jedoch nicht ordnungsgemäß für Sprachen außerhalb der mehrsprachigen Basisebene, z. B. Osage:

CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 0

Der Grund, warum diese Methode falsche Ergebnisse für Osage-Text zurückgibt, besteht darin, dass die char Instanzen für Osage-Buchstaben Surrogate-Codepunkte sind. Kein einzelner Ersatzcodepunkt verfügt über genügend Informationen, um festzustellen, ob es sich um einen Buchstaben handelt.

Wenn Sie diesen Code so ändern, dass er anstelle verwendet charwirdRune, funktioniert die Methode ordnungsgemäß mit Codepunkten außerhalb der Mehrsprachigen Standardebene:

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

Hier sehen Sie den entsprechenden Code, der mit einem ReadOnlySpan<char>:

static int CountLetters(ReadOnlySpan<char> span)
{
    int letterCount = 0;

    foreach (Rune rune in span.EnumerateRunes())
    {
        if (Rune.IsLetter(rune))
        { letterCount++; }
    }

    return letterCount;
}

Der vorangehende Code zählt Osage-Buchstaben richtig:

CountLettersInString("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟")
// Returns 8

Code, der Surrogatepaare explizit behandelt

Erwägen Sie die Verwendung des Rune Typs, wenn Ihr Code APIs aufruft, die explizit auf Surrogate-Codepunkten arbeiten, z. B. die folgenden Methoden:

Die folgende Methode hat z. B. spezielle Logik zum Umgang mit Ersatzpaaren 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.");
        }
    }
}

Dieser Code ist einfacher, wenn er Runeverwendet wird, wie im folgenden Beispiel:

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
    }
}

Ungeeignete Fälle für Rune

Sie müssen den Rune Typ nicht verwenden, wenn Ihr Code:

  • Sucht nach genauen char Übereinstimmungen
  • Teilt eine Zeichenfolge auf einen bekannten Zeichenwert auf.

Die Verwendung des Typs Rune gibt möglicherweise falsche Ergebnisse zurück, wenn Ihr Code:

  • Zählt die Anzahl der Anzeigezeichen in einem string

Suchen nach exakten char Übereinstimmungen

Der folgende Code durchläuft eine string Suche nach bestimmten Zeichen und gibt den Index der ersten Übereinstimmung zurück. Dieser Code Runemuss nicht geändert werden, da der Code nach Zeichen sucht, die durch einen einzelnen chardargestellt werden.

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
}

Teilen einer Zeichenfolge auf einer bekannten char

Es ist üblich, Trennzeichen wie ' ' (Leerzeichen) oder ',' (Komma) aufzurufen string.Split und zu verwenden, wie im folgenden Beispiel:

string inputString = "🐂, 🐄, 🐆";
string[] splitOnSpace = inputString.Split(' ');
string[] splitOnComma = inputString.Split(',');

Hier ist keine Verwendung Rune erforderlich, da der Code nach Zeichen sucht, die durch einen einzelnen chardargestellt werden.

Zählen der Anzahl der Anzeigezeichen in einem string

Die Anzahl der Rune Instanzen in einer Zeichenfolge entspricht möglicherweise nicht der Anzahl von benutzerdefinierten Zeichen, die beim Anzeigen der Zeichenfolge angezeigt werden.

Da Rune Instanzen Unicode-Skalarwerte darstellen, können Komponenten, die den Unicode-Textsegmentierungsrichtlinien entsprechen, als Baustein für das Zählen von Anzeigezeichen verwendet werden Rune .

Der StringInfo Typ kann verwendet werden, um Anzeigezeichen zu zählen, jedoch nicht in allen Szenarien für .NET-Implementierungen, die nicht .NET 5+ sind.

Weitere Informationen finden Sie unter Grapheme-Cluster.

Instanziieren eines Rune

Es gibt mehrere Möglichkeiten zum Abrufen einer Rune Instanz. Sie können einen Konstruktor verwenden, um einen Rune direkt aus:

  • Ein Codepunkt.

    Rune a = new Rune(0x0061); // LATIN SMALL LETTER A
    Rune b = new Rune(0x10421); // DESERET CAPITAL LETTER ER
    
  • Eine einzelne char.

    Rune c = new Rune('a');
    
  • Ein Ersatzpaar char .

    Rune d = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
    

Alle Konstruktoren lösen einen ArgumentException Aus, wenn die Eingabe keinen gültigen Unicode-Skalarwert darstellt.

Es stehen Rune.TryCreate Methoden für Aufrufer zur Verfügung, die keine Ausnahmen für Fehler auslösen möchten.

Rune Instanzen können auch aus vorhandenen Eingabesequenzen gelesen werden. Wenn beispielsweise UTF-16-Daten ReadOnlySpan<char> dargestellt werden, gibt die Rune.DecodeFromUtf16 Methode die erste Rune Instanz am Anfang der Eingabespanne zurück. Die Rune.DecodeFromUtf8 Methode funktioniert ähnlich und akzeptiert einen ReadOnlySpan<byte> Parameter, der UTF-8-Daten darstellt. Es gibt entsprechende Methoden zum Lesen vom Ende der Spanne anstelle des Anfangs der Spanne.

Abfrageeigenschaften eines Rune

Verwenden Sie die Rune.Value Eigenschaft, um den ganzzahligen Codepunktwert einer Rune Instanz abzurufen.

Rune rune = new Rune('\ud83d', '\udd2e'); // U+1F52E CRYSTAL BALL
int codePoint = rune.Value; // = 128302 decimal (= 0x1F52E)

Viele der statischen APIs, die für den char Typ verfügbar sind, sind auch für den Rune Typ verfügbar. Beispielsweise Rune.IsWhiteSpace sind sie Rune.GetUnicodeCategory Entsprechungen Char.IsWhiteSpace und Char.GetUnicodeCategory Methoden. Die Rune Methoden behandeln Ersatzpaare richtig.

Der folgende Beispielcode verwendet eine ReadOnlySpan<char> Eingabe und schneidet sowohl vom Anfang als auch vom Ende der Spanne ab Rune , bei der es sich nicht um einen Buchstaben oder eine Ziffer handelt.

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;
}

Es gibt einige API-Unterschiede zwischen char und Rune. Beispiel:

Konvertieren einer Rune in UTF-8 oder UTF-16

Da es sich bei einem Rune Unicode-Skalarwert um einen Unicode-Skalarwert handelt, kann er in UTF-8-, UTF-16- oder UTF-32-Codierung konvertiert werden. Der Rune Typ verfügt über integrierte Unterstützung für die Konvertierung in UTF-8 und UTF-16.

Die Rune.EncodeToUtf16 Instanz wird in char Instanzen konvertiertRune. Verwenden Sie die Rune.Utf16SequenceLength Eigenschaft, um die Anzahl der char Instanzen abzufragen, die sich aus der Konvertierung einer Rune Instanz in UTF-16 ergeben würden. Für die UTF-8-Konvertierung sind ähnliche Methoden vorhanden.

Im folgenden Beispiel wird eine Rune Instanz in ein char Array konvertiert. Der Code geht davon aus, dass Sie eine Rune Instanz in der rune Variablen haben:

char[] chars = new char[rune.Utf16SequenceLength];
int numCharsWritten = rune.EncodeToUtf16(chars);

Da eine string Sequenz von UTF-16-Zeichen ist, konvertiert das folgende Beispiel auch eine Rune Instanz in UTF-16:

string theString = rune.ToString();

Im folgenden Beispiel wird eine Rune Instanz in ein UTF-8 Bytearray konvertiert:

byte[] bytes = new byte[rune.Utf8SequenceLength];
int numBytesWritten = rune.EncodeToUtf8(bytes);

Die Rune.EncodeToUtf16 Methoden geben Rune.EncodeToUtf8 die tatsächliche Anzahl geschriebener Elemente zurück. Sie lösen eine Ausnahme aus, wenn der Zielpuffer zu kurz ist, um das Ergebnis zu enthalten. Es gibt auch nicht auslösende TryEncodeToUtf8 Methoden und TryEncodeToUtf16 Methoden für Aufrufer, die Ausnahmen vermeiden möchten.

Rune in .NET vs. anderen Sprachen

Der Begriff "Rune" ist im Unicode-Standard nicht definiert. Der Begriff stammt aus der Erstellung von UTF-8. Rob Pike und Ken Thompson suchten nach einem Begriff, um zu beschreiben, was schließlich als Codepunkt bekannt werden würde. Sie beglichen den Begriff "Rune", und Rob Pikes späterer Einfluss auf die Go-Programmiersprache half, den Begriff zu popularisieren.

Der .NET-Typ Rune entspricht jedoch nicht dem Go-Typ rune . In Go ist der rune Typ ein Alias für int32. Ein Go-Rune ist für einen Unicode-Codepunkt vorgesehen, kann jedoch ein beliebiger 32-Bit-Wert sein, einschließlich ersatzweiser Codepunkte und Werte, die keine rechtlichen Unicode-Codepunkte sind.

Ähnliche Typen in anderen Programmiersprachen finden Sie unter Rusts Grundtyp char oder Swifts Unicode.Scalar Typ, die beide Unicode-Skalarwerte darstellen. Sie bieten Funktionen ähnlich wie . Nets Rune Typ und sie verbieten die Instanziierung von Werten, die keine zulässigen Unicode-Skalarwerte sind.