Freigeben über


System.Text.Rune-Struct

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

Eine Rune Instanz steht für einen Unicode-Skalarwert, das heißt, einen beliebigen Codepunkt mit Ausnahme des Ersatzbereichs (U+D800..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, Code-Punkt, Surrogat-Bereich und wohlgeformt nicht vertraut sind, lesen Sie Einführung in die Zeichenkodierung 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
  • Explizite Handles für Surrogatpaare

APIs, die Unicode-Skalarwerte erfordern

Wenn Ihr Code durch die char-Instanzen in einem string oder ReadOnlySpan<char> iteriert, funktionieren einige der char-Methoden bei char-Instanzen, die sich im Surrogatbereich befinden, nicht korrekt. 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 ist ein entsprechender Code, der mit einem ReadOnlySpan<char> funktioniert:

// 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 Rune durch char ersetzt, funktioniert die Methode ordnungsgemäß mit Codepunkten außerhalb der Basis-Multilingual-Ebene:

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 ist ein entsprechender Code, der mit einem ReadOnlySpan<char> funktioniert:

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 die Osage-Buchstaben korrekt:

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

Wann Rune nicht verwendet werden sollte

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

  • Sucht nach exakten char Übereinstimmungen
  • Teilt eine Zeichenfolge nach einem bekannten Zeichenwert

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

  • Zählt die Anzahl der Bildschirm-Zeichen in einem string

Sucht nach exakten char Übereinstimmungen

Der folgende Code durchläuft ein string auf der Suche nach bestimmten Zeichen und gibt den Index der ersten Übereinstimmung zurück. Dieser Code muss nicht geändert werden, um Rune zu verwenden, da der Code nach Zeichen sucht, die durch einen einzelnen char dargestellt 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
}

Teilt eine Zeichenfolge auf einen bekannten char

Es ist üblich, string.Split aufzurufen und Trennzeichen wie ' ' (Leerzeichen) oder ',' (Komma) 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ählt die Anzahl der Bildschirm-Zeichen 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 Typ StringInfo kann zum Zählen von Bildschirm-Zeichen verwendet werden, aber er zählt nicht in allen Szenarien für andere .NET Implementierungen als .NET 5+ korrekt.

Weitere Informationen finden Sie unter Grapheme-Cluster.

Wie man ein Rune instanziiert

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

  • 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 Surrogat char Paar.

    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 gibt Rune.TryCreate Methoden für Aufrufer, die nicht möchten, dass bei einem Fehler eine Ausnahme ausgelöst wird.

Rune Instanzen können auch aus vorhandenen Eingabesequenzen gelesen werden. Wenn ein ReadOnlySpan<char> beispielsweise UTF-16-Daten darstellt, 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 äquivalente Methoden, um vom Ende des Spans zu lesen, anstatt vom Anfang des Spans.

Abfrageeigenschaften eines Rune

Verwenden Sie die Rune Eigenschaft, um den ganzzahligen Codepunktwert einer Rune.Value 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 sind Rune.IsWhiteSpace und Rune.GetUnicodeCategory Entsprechungen von Char.IsWhiteSpace- und Char.GetUnicodeCategory-Methoden. Die Rune Methoden behandeln Ersatzpaare richtig.

Der folgende Beispielcode nimmt eine ReadOnlySpan<char> als Eingabe und entfernt sowohl vom Anfang als auch vom Ende des Abschnitts jedes Rune, das kein Buchstabe oder Ziffer ist.

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 konvertiert eine Rune Instanz in char Instanzen. Verwenden Sie die char Eigenschaft, um die Anzahl der Rune Instanzen abzufragen, die sich aus der Konvertierung einer Rune.Utf16SequenceLength 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 Methoden Rune.EncodeToUtf16 und Rune.EncodeToUtf8 geben 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. Für Aufrufer, die Ausnahmen vermeiden möchten, gibt es auch TryEncodeToUtf8 und TryEncodeToUtf16 Methoden, die keine Ausnahmen auslösen.

Rune in .NET im Vergleich zu 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 einigten sich auf den Begriff "Rune", und Rob Pikes Einfluss zu einem späteren Zeitpunkt 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.