System.Text.Rune – struktura

Poznámka:

Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.

Rune Instance představuje skalární hodnotu Unicode, což znamená libovolný bod kódu s výjimkou náhradního rozsahu (U+D800..U+DFFF). Konstruktory a konverzní operátory typu ověřují vstup, takže uživatelé mohou volat rozhraní API za předpokladu, že je základní Rune instance správně vytvořená.

Pokud neznáte termíny skalární hodnota Unicode, kódový bod, náhradní rozmezí a dobře formátovaný, přečtěte si téma Úvod do kódování znaků v .NET.

Kdy použít typ Rune

Zvažte použití typu Rune, pokud váš kód:

  • Volá rozhraní API, která vyžadují skalární hodnoty Unicode.
  • Explicitní zpracování náhradních dvojic

Rozhraní API, která vyžadují skalární hodnoty Unicode

Pokud váš kód prochází char instancemi v string nebo ReadOnlySpan<char>, některé z char metod nebudou správně fungovat na char instancích, které jsou v surrogate range. Například následující rozhraní API vyžadují, aby skalární hodnota char fungovala správně:

Následující příklad ukazuje kód, který nebude správně fungovat, pokud některé z char instancí jsou náhradní body kódu:

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

Tady je ekvivalentní kód, který funguje s 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;
}

Předchozí kód funguje správně s některými jazyky, jako je angličtina:

CountLettersInString("Hello")
// Returns 5

Ale nebude fungovat správně pro jazyky mimo základní vícejazyčnou rovinu, například Osage:

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

Důvodem, proč tato metoda vrací nesprávné výsledky pro text Osage je, že char instance písmen Osage jsou náhradní body kódu. Žádný bod náhradního kódu nemá dostatek informací k určení, jestli se jedná o písmeno.

Pokud změníte tento kód na to, aby se používal Rune místo char, metoda funguje správně s kódovými body mimo základní vícejazyčnou rovinu.

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

Tady je ekvivalentní kód, který funguje s ReadOnlySpan<char>:

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

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

    return letterCount;
}

Předchozí kód počítá správně písmena Osage:

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

Kód, který explicitně zpracovává náhradní páry

Zvažte použití Rune typu, pokud váš kód volá rozhraní API, která explicitně pracují s náhradními body kódu, například následující metody:

Například následující metoda má speciální logiku pro řešení náhradních char párů:

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.");
        }
    }
}

Takový kód je jednodušší, pokud používá Rune, jako v následujícím příkladu:

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

Kdy se nepoužívá Rune

Pokud váš kód splňuje podmínky, nemusíte používat typ Rune.

  • Hledá přesné char shody.
  • Rozdělí řetězec podle zadaného znaku.

Použití typu Rune může vrátit nesprávné výsledky, pokud váš kód:

  • Spočítá počet zobrazovaných znaků v string

Vyhledání přesných char shod

Následující kód prochází hledáním string konkrétních znaků a vrátí index první shody. Tento kód není nutné měnit tak, aby se používal Rune, protože kód hledá znaky, které jsou reprezentovány jedním 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
}

Rozděl řetězec na známé char

Je běžné volat string.Split a používat oddělovače, jako je ' ' (mezera) nebo ',' (čárka), jak je znázorněno v následujícím příkladu:

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

Zde není nutné používat Rune , protože kód hledá znaky, které jsou reprezentovány jedním char.

Spočítat počet zobrazovaných znaků v string

Počet Rune instancí v řetězci nemusí odpovídat počtu uživatelsky srozumitelných znaků zobrazených při zobrazení řetězce.

Vzhledem k tomu, že Rune instance představují skalární hodnoty Unicode, mohou komponenty, které dodržují pokyny pro segmentaci textu Unicode , použít Rune jako stavební blok pro počítání zobrazovaných znaků.

Typ StringInfo lze použít k počítání zobrazovaných znaků, ale ve všech scénářích jiných než .NET 5 nebo novějších se nepočítá správně.

Další informace najdete v tématu Graphemové clustery.

Jak vytvořit instanci Rune

Existuje několik způsobů, jak získat Rune instanci. Konstruktor můžete použít k vytvoření Rune přímo z:

  • Bod kódu.

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

    Rune c = new Rune('a');
    
  • Náhradní char pár.

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

Všechny konstruktory vyvolají výjimku ArgumentException, pokud vstup nepředstavuje platnou skalární hodnotu Unicode.

Pro volající, kteří nechtějí, aby při selhání docházelo k vyvolání výjimek, jsou dostupné metody Rune.TryCreate.

Rune Instance lze také číst z existujících vstupních sekvencí. Například pokud ReadOnlySpan<char> představuje data UTF-16, metoda Rune.DecodeFromUtf16 vrátí první výskyt Rune na začátku vstupního úseku. Metoda Rune.DecodeFromUtf8 funguje podobně a přijímá ReadOnlySpan<byte> parametr, který představuje data UTF-8. Jsou k dispozici ekvivalentní metody pro čtení od konce rozsahu místo od začátku rozsahu.

Vlastnosti dotazu Rune

Chcete-li získat celočíselnou hodnotu Rune bodu kódu instance, použijte Rune.Value vlastnost.

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

Mnoho statických rozhraní API dostupných pro typ char je také k dispozici pro typ Rune. Například jsou Rune.IsWhiteSpace a Rune.GetUnicodeCategory ekvivalenty k metodám Char.IsWhiteSpace a Char.GetUnicodeCategory. Metody Rune správně zpracovávají náhradní páry.

Následující příklad kódu přebírá ReadOnlySpan<char> jako vstup a ořízne z počátečního i koncového rozsahu každý Rune , který není písmenem nebo číslicí.

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

Mezi char a Rune existují určité rozdíly v API. Například:

Převeďte Rune na UTF-8 nebo UTF-16

Rune Vzhledem k tomu, že je skalární hodnota Unicode, lze ji převést na kódování UTF-8, UTF-16 nebo UTF-32. Typ Rune má integrovanou podporu převodu na UTF-8 a UTF-16.

Rune.EncodeToUtf16 Převede Rune instanci na char instance. Chcete-li zjistit počet instancí char, které by byly výsledkem převodu instance Rune na UTF-16, použijte vlastnost Rune.Utf16SequenceLength. Podobné metody existují pro převod UTF-8.

Následující příklad převede Rune instanci na char pole. Kód předpokládá, že máte instanci Rune v proměnné rune.

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

Vzhledem k tomu, že string je posloupností znaků UTF-16, následující příklad také převede instanci Rune na UTF-16:

string theString = rune.ToString();

Následující příklad převede Rune instanci na bajtové UTF-8 pole:

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

Metody Rune.EncodeToUtf16 a Rune.EncodeToUtf8 vrátí skutečný počet zapsaných prvků. Vyvolají výjimku, pokud je cílová vyrovnávací paměť příliš krátká, aby obsahovala výsledek. Existují i metody TryEncodeToUtf8 a TryEncodeToUtf16 pro volající, kteří se chtějí vyhnout výjimkám.

Runy v .NET vs. jiných jazycích

Termín rune není definován ve standardu Unicode. Termín pochází zpět k vytvoření UTF-8. Rob Pike a Ken Thompson hledali termín, který by nakonec popsal, co by se nakonec stalo známým jako kódový bod. Dohodli se na označení "rune", a později vliv Roba Pika na programovací jazyk Go pomohl tento termín zpopularizovat.

Typ .NET Rune však není ekvivalentem typu Go rune . V Go je rune typ alias pro int32. Runa Go má představovat kódový bod Unicode, ale může to být jakákoli 32bitová hodnota, včetně náhradních kódových bodů a hodnot, které nejsou legálními kódovými body Unicode.

Podobné typy v jiných programovacích jazycích najdete, viz primitivní typ char Rustu nebo typ Unicode.Scalar Swiftu, které oba představují skalární hodnoty Unicode. Poskytují funkce podobné typu Rune v .NET a nepovolují vytváření instancí hodnot, které nejsou platnými skalárními hodnotami Unicode.