System.Text.Rune, struktura

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Rune Wystąpienie reprezentuje wartość skalarną Unicode, co oznacza, że dowolny punkt kodu z wyłączeniem zakresu zastępczego (U+D800.). U+DFFF). Konstruktory i operatory konwersji typu weryfikują dane wejściowe, aby użytkownicy mogli wywoływać interfejsy API przy założeniu, że wystąpienie bazowe Rune jest prawidłowo sformułowane.

Jeśli nie znasz terminów Wartość skalarna Unicode, punkt kodu, zakres zastępczy i dobrze sformułowany, zobacz Wprowadzenie do kodowania znaków na platformie .NET.

Kiedy należy użyć typu Rune

Rozważ użycie Rune typu, jeśli kod:

  • Wywołuje interfejsy API, które wymagają wartości skalarnych Unicode
  • Jawnie obsługuje pary zastępcze

Interfejsy API wymagające wartości skalarnych Unicode

Jeśli kod wykonuje iterację po char wystąpieniach w obiekcie string lub ReadOnlySpan<char>, niektóre char metody nie będą działać poprawnie w char wystąpieniach, które znajdują się w zakresie zastępczym. Na przykład następujące interfejsy API wymagają poprawnego działania wartości char skalarnej:

W poniższym przykładzie pokazano kod, który nie będzie działał poprawnie, jeśli którykolwiek z char wystąpień jest zastępczymi punktami kodu:

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

Oto równoważny kod, który działa z elementem 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;
}

Powyższy kod działa poprawnie w niektórych językach, takich jak angielski:

CountLettersInString("Hello")
// Returns 5

Jednak nie będzie działać poprawnie w przypadku języków spoza podstawowej płaszczyzny wielojęzycznej, takiej jak Osage:

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

Powodem, dla którego ta metoda zwraca nieprawidłowe wyniki dla tekstu systemu operacyjnego, jest to, że char wystąpienia liter systemu operacyjnego są punktami kodu zastępczymi. Żaden pojedynczy punkt kodu zastępczego nie ma wystarczającej ilości informacji, aby ustalić, czy jest to litera.

Jeśli zmienisz ten kod tak, aby używał zamiast Runecharmetody , metoda działa poprawnie z punktami kodu poza podstawową płaszczyzną wielojęzyczną:

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

Oto równoważny kod, który działa z elementem ReadOnlySpan<char>:

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

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

    return letterCount;
}

Powyższy kod poprawnie zlicza litery Osage:

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

Kod, który jawnie obsługuje pary zastępcze

Rozważ użycie Rune typu, jeśli kod wywołuje interfejsy API, które jawnie działają w punktach kodu zastępczego, takich jak następujące metody:

Na przykład następująca metoda ma specjalną logikę do obsługi par zastępczych 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.");
        }
    }
}

Taki kod jest prostszy, jeśli używa Runemetody , jak w poniższym przykładzie:

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

Kiedy nie używać atrybutu Rune

Nie musisz używać Rune typu, jeśli kod:

  • Szuka dokładnych char dopasowań
  • Dzieli ciąg na znanej wartości char

Rune Użycie typu może zwracać nieprawidłowe wyniki, jeśli kod:

  • Zlicza liczbę znaków wyświetlanych w obiekcie string

Wyszukiwanie dokładnych char dopasowań

Poniższy kod iteruje wyszukiwanie string określonych znaków, zwracając indeks pierwszego dopasowania. Nie trzeba zmieniać tego kodu tak, aby używał Runemetody , ponieważ kod szuka znaków reprezentowanych przez pojedynczy charelement .

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
}

Dzielenie ciągu na znany char

Często należy wywoływać string.Split ograniczniki, takie jak ' ' (spacja) lub ',' (przecinek), jak w poniższym przykładzie:

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

Nie ma tutaj potrzeby użycia Rune , ponieważ kod szuka znaków reprezentowanych przez pojedynczy charelement .

Zlicz liczbę znaków wyświetlanych w obiekcie string

Liczba Rune wystąpień w ciągu może być niezgodna z liczbą znaków, które można zobaczyć podczas wyświetlania ciągu.

Ponieważ Rune wystąpienia reprezentują wartości skalarne Unicode, składniki zgodne z wytycznymi dotyczącymi segmentacji tekstu Unicode mogą być używane Rune jako blok konstrukcyjny do zliczania znaków wyświetlanych.

Typ StringInfo może służyć do zliczania znaków wyświetlania, ale nie jest on poprawnie liczone we wszystkich scenariuszach dla implementacji platformy .NET innych niż .NET 5+.

Aby uzyskać więcej informacji, zobacz Grapheme clusters (Klastry Grapheme).

Jak utworzyć wystąpienie obiektu Rune

Istnieje kilka sposobów uzyskiwania Rune wystąpienia. Konstruktora można użyć do utworzenia obiektu Rune bezpośrednio z:

  • Punkt kodu.

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

    Rune c = new Rune('a');
    
  • Para zastępcza char .

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

Wszystkie konstruktory zgłaszają wartość ArgumentException , jeśli dane wejściowe nie reprezentują prawidłowej wartości skalarnej Unicode.

Rune.TryCreate Istnieją metody dostępne dla osób wywołujących, którzy nie chcą zgłaszać wyjątków w przypadku awarii.

Rune wystąpienia mogą być również odczytywane z istniejących sekwencji wejściowych. Na przykład, biorąc pod uwagę ReadOnlySpan<char> , że reprezentuje dane UTF-16, Rune.DecodeFromUtf16 metoda zwraca pierwsze Rune wystąpienie na początku zakresu danych wejściowych. Metoda Rune.DecodeFromUtf8 działa podobnie, akceptując ReadOnlySpan<byte> parametr reprezentujący dane UTF-8. Istnieją równoważne metody odczytywania od końca zakresu zamiast początku zakresu.

Właściwości kwerendy obiektu Rune

Aby uzyskać wartość punktu kodu liczby całkowitej Rune wystąpienia, użyj Rune.Value właściwości .

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

Wiele statycznych interfejsów API dostępnych w typie char jest również dostępnych w typie Rune . Na przykład Rune.IsWhiteSpace metody i Rune.GetUnicodeCategory są równoważne Char.IsWhiteSpace metodom i .Char.GetUnicodeCategory Metody Rune poprawnie obsługują pary zastępcze.

Poniższy przykładowy kod przyjmuje ReadOnlySpan<char> jako dane wejściowe i przycina zarówno od początku, jak i na końcu zakresu, który Rune nie jest literą ani cyfrą.

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

Istnieją pewne różnice między interfejsem API a charRune. Na przykład:

Konwertowanie klasy Rune na utF-8 lub UTF-16

Rune Ponieważ element jest wartością skalarną Unicode, można go przekonwertować na kodowanie UTF-8, UTF-16 lub UTF-32. Typ Rune ma wbudowaną obsługę konwersji na utF-8 i UTF-16.

Element Rune.EncodeToUtf16 konwertuje Rune wystąpienie na char wystąpienia. Aby zbadać liczbę char wystąpień, które mogłyby wynikać z konwersji Rune wystąpienia na UTF-16, użyj Rune.Utf16SequenceLength właściwości . Podobne metody istnieją dla konwersji UTF-8.

Poniższy przykład konwertuje Rune wystąpienie na tablicę char . W kodzie przyjęto założenie, Rune że masz wystąpienie w zmiennej rune :

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

Ponieważ element jest string sekwencją znaków UTF-16, poniższy przykład konwertuje Rune również wystąpienie na utF-16:

string theString = rune.ToString();

Poniższy przykład konwertuje Rune wystąpienie na tablicę bajtów UTF-8 :

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

Metody Rune.EncodeToUtf16 i Rune.EncodeToUtf8 zwracają rzeczywistą liczbę zapisanych elementów. Zgłaszają wyjątek, jeśli bufor docelowy jest zbyt krótki, aby zawierać wynik. Istnieją metody bez zgłaszania TryEncodeToUtf8 , TryEncodeToUtf16 a także dla osób wywołujących, którzy chcą uniknąć wyjątków.

Uruchamianie na platformie .NET a w innych językach

Termin "rune" nie jest zdefiniowany w standardzie Unicode. Termin sięga tworzenia utF-8. Rob Pike i Ken Thompson szukali terminu, aby opisać, co ostatecznie stanie się znane jako punkt kodu. Osiedlili się na termin "rune", a później wpływ Roba Pike'a na język programowania Go pomógł popularyzować termin.

Jednak typ platformy .NET Rune nie jest odpowiednikiem typu Go rune . W języku rune Go typ jest aliasem dla elementu int32. Element Rune języka Go ma reprezentować punkt kodu Unicode, ale może to być dowolna wartość 32-bitowa, w tym zastępcze punkty kodu i wartości, które nie są legalnymi punktami kodu Unicode.

Aby zapoznać się z podobnymi typami w innych językach programowania, zobacz Typ pierwotny char Rust lub typ swiftUnicode.Scalar, z których oba reprezentują wartości skalarne Unicode. Zapewniają one funkcje podobne do . Typ platformy Rune NET i nie zezwalają na tworzenie wystąpień wartości, które nie są legalnymi wartościami skalarnymi Unicode.