Udostępnij przez


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 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 obiekt bazowy Rune jest poprawnie utworzony.

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

Kiedy używać typu Rune

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

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

Interfejsy programowania aplikacji (API) wymagające wartości skalarnych Unicode

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

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 Osage, jest to, że wystąpienia liter Osage 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, aby używał Rune zamiast char, 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 typu Rune, jeśli kod wywołuje interfejsy API, które jawnie działają na punktach kodowych zastępczych, 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 Rune, 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 należy używać Rune

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

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

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

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

Szukaj dokładnych dopasowań char

Kod przechodzi przez string, szukając określonych znaków, zwracając indeks pierwszego dopasowania. Nie trzeba zmieniać tego kodu, aby używał Rune, ponieważ kod szuka znaków reprezentowanych przez pojedynczy char.

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
}

Podziel ciąg znaków na podstawie znanego char

Często należy uruchomić string.Split i używać separatorów, takich 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 .

Policz liczbę znaków wyświetlanych w 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ą używać Rune jako bloku konstrukcyjnego do zliczania znaków wyświetlania.

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

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

Jak utworzyć wystąpienie obiektu Rune

Są różne sposoby, aby uzyskać instancję Rune. Konstruktora można użyć do utworzenia obiektu Rune bezpośrednio z:

  • Punkt kodowy.

    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, jeśli chodzi o ReadOnlySpan<char>, który reprezentuje dane w UTF-16, metoda Rune.DecodeFromUtf16 zwraca pierwsze Rune wystąpienie na początku prostokąta 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ść liczby całkowitej punktu kodu instancji Rune, użyj właściwości Rune.Value.

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 i Rune.GetUnicodeCategory są równoważne metodom Char.IsWhiteSpace 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 z początku, jak i z końca ciągu każdy Rune, który 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 char a Rune w zakresie API. Przykład:

Konwertuj 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 wystąpienie Rune na wystąpienia char. Aby sprawdzić liczbę char wystąpień, które wynikają z konwersji Rune wystąpienia na UTF-16, skorzystaj z właściwości Rune.Utf16SequenceLength. Podobne metody istnieją dla konwersji UTF-8.

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

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

Ponieważ string jest sekwencją znaków UTF-16, poniższy przykład konwertuje wystąpienie Rune na UTF-16.

string theString = rune.ToString();

Poniższy przykład konwertuje wystąpienie Rune 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ą również metody TryEncodeToUtf8 i TryEncodeToUtf16 , które nie powodują zgłaszania wyjątków, dla wywołujących chcących ich uniknąć.

"Rune w .NET w porównaniu z innymi językami"

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

Jednak typ platformy .NET Rune nie jest odpowiednikiem typu Go rune . W języku rune Go typ jest aliasem dla int32. Runa w języku Go powinna reprezentować punkt kodowy Unicode, ale może to być dowolna 32-bitowa wartość, w tym zastępcze punkty kodowe i wartości, które nie są prawidłowymi punktami kodowymi 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 funkcjonalność podobną do typu Rune z platformy .NET i nie zezwalają na tworzenie instancji wartości, które nie są prawidłowymi wartościami skalarnymi Unicode.