System.Text.Rune – struktura

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á, že jakýkoli 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, bod kódu, náhradní oblast 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

Pokud kód používáte, Rune zvažte použití typu:

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

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

Pokud váš kód prochází char instancemi v string nebo a ReadOnlySpan<char>, některé z char metod nebudou správně fungovat na char instancích, které jsou v náhradním rozsahu. 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 tak, aby se místo toho používal Runechar, metoda funguje správně s body kódu 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 nepoužívat atribut Rune

Pokud kód používáte, nemusíte ho Rune používat:

  • Hledá přesné char shody.
  • Rozdělí řetězec na známou hodnotu znaku.

Rune Použití typu 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ělení řetězce 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 Clustery Grapheme.

Vytvoření instance instance 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
    
  • Jeden .char

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

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

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

Rune.TryCreate Existují metody pro volající, kteří nechtějí, aby výjimky byly vyvolány při selhání.

Rune Instance lze také číst z existujících vstupních sekvencí. Například vzhledem k ReadOnlySpan<char> tomu, že představuje data UTF-16, Rune.DecodeFromUtf16 metoda vrátí první Rune instanci na začátku vstupního rozsahu. Metoda Rune.DecodeFromUtf8 funguje podobně a přijímá ReadOnlySpan<byte> parametr, který představuje data UTF-8. Existují ekvivalentní metody pro čtení z konce rozsahu místo 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 char daný typ je také k dispozici pro daný Rune typ. Jsou to například Rune.IsWhiteSpaceRune.GetUnicodeCategory ekvivalenty Char.IsWhiteSpace a Char.GetUnicodeCategory metody. 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 rozhraním RuneAPI char a . Příklad:

Převod na Rune 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 Převede Rune.EncodeToUtf16 instanci na char instance. Chcete-li dotazovat počet char instancí, které by výsledkem převodu Rune instance na UTF-16, použijte Rune.Utf16SequenceLength vlastnost. 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 Rune v rune proměnné instanci:

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

string Vzhledem k tomu, že je posloupnost znaků UTF-16, následující příklad také převede Rune instanci 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ů. Vyvolá výjimku, pokud je cílová vyrovnávací paměť příliš krátká, aby obsahovala výsledek. Volajícím, kteří se chtějí vyhnout výjimkám, existují i metody, které nevyvolávají TryEncodeToUtf8TryEncodeToUtf16 .

Spuštění v .NET a 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. Usadili se na termínu "rune", a Rob Pike později vliv na programovací jazyk Go pomohl popularizovat termín.

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

Podobné typy v jiných programovacích kódech najdete v jiných programovacích jazycích, viz primitivní typ Rustu nebo SwiftůvUnicode.Scalar typchar Poskytují funkce podobné . Net typ Rune a nepovolují vytváření instancí hodnot, které nejsou právními skalárními hodnotami Unicode.