System.Text.Rune struct

Cet article vous offre des remarques complémentaires à la documentation de référence pour cette API.

Une Rune instance représente une valeur scalaire Unicode, ce qui signifie tout point de code à l’exclusion de la plage de substitution (U+D800.). U+DFFF). Les constructeurs et opérateurs de conversion du type valident l’entrée, afin que les consommateurs puissent appeler les API en supposant que l’instance sous-jacente Rune est bien formée.

Si vous n’êtes pas familiarisé avec les termes « valeur scalaire Unicode », point de code, plage de substitution et bien formé, consultez Présentation de l’encodage de caractères dans .NET.

Quand utiliser le type Rune

Envisagez d’utiliser le Rune type si votre code :

  • Appelle des API qui nécessitent des valeurs scalaires Unicode
  • Gère explicitement les paires de substitution

API qui nécessitent des valeurs scalaires Unicode

Si votre code itère dans les char instances d’un string ou d’un ReadOnlySpan<char>, certaines des char méthodes ne fonctionnent pas correctement sur char les instances qui se trouvent dans la plage de substitution. Par exemple, les API suivantes nécessitent une valeur char scalaire pour fonctionner correctement :

L’exemple suivant montre le code qui ne fonctionnera pas correctement si l’une char des instances est des points de code de substitution :

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

Voici un code équivalent qui fonctionne avec un 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;
}

Le code précédent fonctionne correctement avec certaines langues telles que l’anglais :

CountLettersInString("Hello")
// Returns 5

Mais cela ne fonctionnera pas correctement pour les langues en dehors du plan multilingue de base, par exemple Osage :

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

La raison pour laquelle cette méthode retourne des résultats incorrects pour le texte Osage est que les char instances des lettres Osage sont des points de code de substitution. Aucun point de code de substitution unique n’a suffisamment d’informations pour déterminer s’il s’agit d’une lettre.

Si vous modifiez ce code à utiliser Rune au lieu de cela, la méthode fonctionne correctement avec des points de charcode en dehors du plan multilingue de base :

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

Voici un code équivalent qui fonctionne avec un ReadOnlySpan<char>:

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

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

    return letterCount;
}

Le code précédent compte correctement les lettres Osage :

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

Code qui gère explicitement les paires de substitution

Envisagez d’utiliser le Rune type si votre code appelle des API qui fonctionnent explicitement sur des points de code de substitution, tels que les méthodes suivantes :

Par exemple, la méthode suivante a une logique spéciale pour traiter les paires de substitution 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.");
        }
    }
}

Ce code est plus simple s’il utilise Rune, comme dans l’exemple suivant :

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

Quand ne pas utiliser Rune

Vous n’avez pas besoin d’utiliser le Rune type si votre code :

  • Recherche des correspondances exactes char
  • Fractionne une chaîne sur une valeur char connue

L’utilisation du Rune type peut retourner des résultats incorrects si votre code :

  • Compte le nombre de caractères d’affichage dans un string

Rechercher des correspondances exactes char

Le code suivant itère à travers une string recherche de caractères spécifiques, retournant l’index de la première correspondance. Il n’est pas nécessaire de modifier ce code pour l’utiliser Rune, car le code recherche des caractères représentés par un seul 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
}

Fractionner une chaîne sur une chaîne connue char

Il est courant d’appeler string.Split et d’utiliser des délimiteurs tels que ' ' (espace) ou ',' (virgule), comme dans l’exemple suivant :

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

Il n’est pas nécessaire d’utiliser Rune ici, car le code recherche des caractères représentés par un seul char.

Compter le nombre de caractères d’affichage dans un string

Le nombre d’instances d’une chaîne peut ne pas correspondre au nombre de caractères perceivables de l’utilisateur affichés lors de Rune l’affichage de la chaîne.

Étant donné que Rune les instances représentent des valeurs scalaires Unicode, les composants qui suivent les instructions de segmentation de texte Unicode peuvent être utilisés Rune comme bloc de construction pour compter les caractères d’affichage.

Le StringInfo type peut être utilisé pour compter les caractères d’affichage, mais il ne compte pas correctement dans tous les scénarios pour les implémentations .NET autres que .NET 5+.

Pour plus d’informations, consultez clusters Grapheme.

Comment instancier un Rune

Il existe plusieurs façons d’obtenir une Rune instance. Vous pouvez utiliser un constructeur pour créer un Rune constructeur directement à partir de :

  • Point de code.

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

    Rune c = new Rune('a');
    
  • Paire de substitution char .

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

Tous les constructeurs lèvent une ArgumentException valeur scalaire Unicode valide si l’entrée ne représente pas de valeur scalaire Unicode valide.

Il existe Rune.TryCreate des méthodes disponibles pour les appelants qui ne veulent pas que les exceptions soient levées en cas d’échec.

Rune les instances peuvent également être lues à partir de séquences d’entrée existantes. Par exemple, compte tenu d’une ReadOnlySpan<char> valeur UTF-16, la Rune.DecodeFromUtf16 méthode retourne la première Rune instance au début de l’étendue d’entrée. La Rune.DecodeFromUtf8 méthode fonctionne de la même façon, acceptant un ReadOnlySpan<byte> paramètre qui représente des données UTF-8. Il existe des méthodes équivalentes pour lire à partir de la fin de l’étendue au lieu du début de l’étendue.

Interroger les propriétés d’un Rune

Pour obtenir la valeur de point de code entier d’une Rune instance, utilisez la Rune.Value propriété.

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

La plupart des API statiques disponibles sur le char type sont également disponibles sur le Rune type. Par exemple, Rune.IsWhiteSpace et Rune.GetUnicodeCategory sont équivalents aux méthodes et Char.GetUnicodeCategory aux Char.IsWhiteSpace méthodes. Les Rune méthodes gèrent correctement les paires de substitution.

L’exemple de code suivant prend une ReadOnlySpan<char> entrée et supprime à la fois du début et de la fin de l’étendue toutes les Rune lettres qui ne sont pas une lettre ou un chiffre.

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

Il existe des différences d’API entre char et Rune. Par exemple :

Convertir un Rune en UTF-8 ou UTF-16

Étant donné qu’il Rune s’agit d’une valeur scalaire Unicode, elle peut être convertie en encodage UTF-8, UTF-16 ou UTF-32. Le Rune type prend en charge la conversion en UTF-8 et UTF-16.

Convertit Rune.EncodeToUtf16 une Rune instance en char instances. Pour interroger le nombre d’instances char résultant de la conversion d’une Rune instance en UTF-16, utilisez la Rune.Utf16SequenceLength propriété. Des méthodes similaires existent pour la conversion UTF-8.

L’exemple suivant convertit une Rune instance en tableau char . Le code suppose que vous disposez d’une Rune instance dans la rune variable :

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

Étant donné qu’une string séquence de caractères UTF-16 est une séquence de caractères, l’exemple suivant convertit également une Rune instance en UTF-16 :

string theString = rune.ToString();

L’exemple suivant convertit une Rune instance en tableau d’octets UTF-8 :

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

Les Rune.EncodeToUtf16 méthodes et Rune.EncodeToUtf8 retournent le nombre réel d’éléments écrits. Ils lèvent une exception si la mémoire tampon de destination est trop courte pour contenir le résultat. Il existe également des méthodes et TryEncodeToUtf16 des méthodes non levées TryEncodeToUtf8 pour les appelants qui souhaitent éviter les exceptions.

Rune dans .NET et d’autres langages

Le terme « rune » n’est pas défini dans la norme Unicode. Le terme remonte à la création de UTF-8. Rob Pike et Ken Thompson cherchaient un terme pour décrire ce qui finirait par devenir un point de code. Ils se sont installés sur le terme « rune », et l’influence ultérieure de Rob Pike sur le langage de programmation Go a contribué à populariser le terme.

Toutefois, le type .NET Rune n’est pas l’équivalent du type Go rune . Dans Go, le rune type est un alias pour int32. Un rune Go est destiné à représenter un point de code Unicode, mais il peut s’agir de n’importe quelle valeur 32 bits, y compris les points de code de substitution et les valeurs qui ne sont pas des points de code Unicode juridiques.

Pour obtenir des types similaires dans d’autres langages de programmation, consultez le type primitif char de Rust ou le Unicode.Scalar type Swift, qui représentent les deux valeurs scalaires Unicode. Ils fournissent des fonctionnalités similaires à . Le type de Rune NET et ils interdisent l’instanciation des valeurs qui ne sont pas des valeurs scalaires Unicode légales.