Bagikan melalui


Struktur System.Text.Rune

Artikel ini menyediakan keterangan tambahan untuk dokumentasi referensi untuk API ini.

Rune Instans mewakili nilai skalar Unicode, yang berarti setiap titik kode tidak termasuk rentang pengganti (U+D800.. U+DFFF). Konstruktor dan operator konversi jenis memvalidasi input, sehingga konsumen dapat memanggil API dengan asumsi bahwa instans yang mendasar Rune terbentuk dengan baik.

Jika Anda tidak terbiasa dengan istilah nilai skalar Unicode, titik kode, rentang pengganti, dan terbentuk dengan baik, lihat Pengenalan pengodean karakter di .NET.

Kapan menggunakan jenis Rune

Pertimbangkan untuk Rune menggunakan jenis jika kode Anda:

  • Memanggil API yang memerlukan nilai skalar Unicode
  • Menangani pasangan pengganti secara eksplisit

API yang memerlukan nilai skalar Unicode

Jika kode Anda berulang melalui char instans dalam atau stringReadOnlySpan<char>, beberapa char metode tidak akan berfungsi dengan benar pada char instans yang berada dalam rentang pengganti. Misalnya, API berikut memerlukan nilai char skalar untuk bekerja dengan benar:

Contoh berikut menunjukkan kode yang tidak akan berfungsi dengan benar jika salah char satu instans adalah titik kode pengganti:

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

Berikut kode yang setara yang berfungsi dengan 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;
}

Kode sebelumnya berfungsi dengan benar dengan beberapa bahasa seperti bahasa Inggris:

CountLettersInString("Hello")
// Returns 5

Tetapi tidak akan berfungsi dengan benar untuk bahasa di luar Basic Multilingual Plane, seperti Osage:

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

Alasan metode ini mengembalikan hasil yang salah untuk teks Osage adalah bahwa char instans untuk huruf Osage adalah titik kode pengganti. Tidak ada satu titik kode pengganti yang memiliki informasi yang cukup untuk menentukan apakah itu huruf.

Jika Anda mengubah kode ini untuk digunakan Rune alih-alih char, metode ini berfungsi dengan benar dengan titik kode di luar Bidang Multibahasa Dasar:

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

Berikut kode yang setara yang berfungsi dengan ReadOnlySpan<char>:

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

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

    return letterCount;
}

Kode sebelumnya menghitung huruf Osage dengan benar:

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

Kode yang secara eksplisit menangani pasangan pengganti

Pertimbangkan untuk Rune menggunakan jenis jika kode Anda memanggil API yang secara eksplisit beroperasi pada titik kode pengganti, seperti metode berikut:

Misalnya, metode berikut memiliki logika khusus untuk menangani pasangan pengganti 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.");
        }
    }
}

Kode tersebut lebih sederhana jika menggunakan Rune, seperti dalam contoh berikut:

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

Kapan tidak seharusnya menggunakan Rune

Anda tidak perlu menggunakan Rune jenis jika kode Anda:

  • Mencari kecocokan yang tepat char
  • Memisahkan string pada nilai karakter yang diketahui

Menggunakan jenis dapat Rune mengembalikan hasil yang salah jika kode Anda:

  • Menghitung jumlah karakter tampilan dalam string

Cari kecocokan yang tepat char

Kode berikut berulang melalui string mencari karakter tertentu, mengembalikan indeks kecocokan pertama. Tidak perlu mengubah kode ini untuk menggunakan Rune, karena kode mencari karakter yang diwakili oleh satu 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
}

Memisahkan string pada yang diketahui char

Umum untuk memanggil string.Split dan menggunakan pemisah seperti ' ' (spasi) atau ',' (koma), seperti dalam contoh berikut:

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

Tidak perlu digunakan Rune di sini, karena kode mencari karakter yang diwakili oleh satu char.

Menghitung jumlah karakter tampilan dalam string

Jumlah Rune instans dalam string mungkin tidak cocok dengan jumlah karakter yang dapat dirasakan pengguna yang ditampilkan saat menampilkan string.

Karena Rune instans mewakili nilai skalar Unicode, komponen yang mengikuti panduan segmentasi teks Unicode dapat digunakan Rune sebagai blok penyusun untuk menghitung karakter tampilan.

StringInfo Jenis dapat digunakan untuk menghitung karakter tampilan, tetapi tidak dihitung dengan benar dalam semua skenario untuk implementasi .NET selain .NET 5+.

Untuk informasi selengkapnya, lihat kluster Grapheme.

Cara membuat instans Rune

Ada beberapa cara untuk mendapatkan Rune instans. Anda dapat menggunakan konstruktor untuk membuat Rune langsung dari:

  • Titik kode.

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

    Rune c = new Rune('a');
    
  • Pasangan pengganti char .

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

Semua konstruktor melempar ArgumentException jika input tidak mewakili nilai skalar Unicode yang valid.

Ada metode yang Rune.TryCreate tersedia untuk penelepon yang tidak ingin pengecualian dilemparkan pada kegagalan.

Rune instans juga dapat dibaca dari urutan input yang ada. Misalnya, mengingat ReadOnlySpan<char> yang mewakili data UTF-16, Rune.DecodeFromUtf16 metode mengembalikan instans pertama Rune di awal rentang input. Metode ini Rune.DecodeFromUtf8 beroperasi sama, menerima ReadOnlySpan<byte> parameter yang mewakili data UTF-8. Ada metode yang setara untuk dibaca dari akhir rentang alih-alih awal rentang.

Properti kueri dari Rune

Untuk mendapatkan nilai titik kode bilangan Rune bulat instans, gunakan Rune.Value properti .

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

Banyak API statis yang tersedia pada char jenisnya juga tersedia pada jenisnya Rune . Misalnya, Rune.IsWhiteSpace dan Rune.GetUnicodeCategory setara dengan Char.IsWhiteSpace metode dan Char.GetUnicodeCategory . Metode Rune menangani pasangan pengganti dengan benar.

Contoh kode berikut mengambil ReadOnlySpan<char> sebagai input dan memangkas dari awal dan akhir rentang setiap Rune yang bukan huruf atau digit.

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

Ada beberapa perbedaan API antara char dan Rune. Misalnya:

Rune Mengonversi ke UTF-8 atau UTF-16

Karena merupakan Rune nilai skalar Unicode, nilai tersebut dapat dikonversi ke pengodean UTF-8, UTF-16, atau UTF-32. Jenis ini Rune memiliki dukungan bawaan untuk konversi ke UTF-8 dan UTF-16.

Mengonversi Rune.EncodeToUtf16 instans Rune menjadi char instans. Untuk mengkueri jumlah char instans yang akan dihasilkan dari mengonversi Rune instans ke UTF-16, gunakan Rune.Utf16SequenceLength properti . Metode serupa ada untuk konversi UTF-8.

Contoh berikut mengonversi instans Rune menjadi char array. Kode mengasumsikan Anda memiliki Rune instans dalam rune variabel:

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

Karena merupakan string urutan karakter UTF-16, contoh berikut juga mengonversi instans Rune ke UTF-16:

string theString = rune.ToString();

Contoh berikut mengonversi instans Rune menjadi UTF-8 array byte:

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

Metode Rune.EncodeToUtf16 dan Rune.EncodeToUtf8 mengembalikan jumlah elemen aktual yang ditulis. Mereka melemparkan pengecualian jika buffer tujuan terlalu pendek untuk berisi hasilnya. Ada non-pelemparan TryEncodeToUtf8 dan TryEncodeToUtf16 metode juga untuk penelepon yang ingin menghindari pengecualian.

Rune di .NET vs. bahasa lain

Istilah "rune" tidak didefinisikan dalam Standar Unicode. Istilah ini kembali ke pembuatan UTF-8. Rob Pike dan Ken Thompson sedang mencari istilah untuk menggambarkan apa yang akhirnya akan dikenal sebagai titik kode. Mereka menetap pada istilah "rune", dan pengaruh Rob Pike kemudian atas bahasa pemrograman Go membantu memprogram istilah tersebut.

Namun, jenis .NET Rune bukan setara dengan jenis Go rune . Di Go, jenisnya rune adalah alias untuk int32. Rune Go dimaksudkan untuk mewakili titik kode Unicode, tetapi dapat berupa nilai 32-bit apa pun, termasuk titik kode pengganti dan nilai yang bukan poin kode Unicode legal.

Untuk jenis serupa dalam bahasa pemrograman lainnya, lihat Jenis primitif char Rust atau jenis SwiftUnicode.Scalar, yang keduanya mewakili nilai skalar Unicode. Mereka menyediakan fungsionalitas yang mirip dengan . Rune Jenis NET, dan melarang instansiasi nilai yang bukan nilai skalar Unicode legal.