System.Text.Rune 結構

本文提供此 API 參考文件的補充備註。

Rune實例代表 Unicode 純量值,這表示排除代理範圍的任何程式代碼點 (U+D800..U+DFFF)。 型別的建構函式和轉換運算子會驗證輸入,因此取用者可以呼叫 API,假設基礎 Rune 實例的格式良好。

如果您不熟悉 Unicode 純量值、程式代碼點、代理範圍和格式正確的詞彙,請參閱 .NET 中的字元編碼簡介。

使用 Rune 類型的時機

如果您的程式代碼, Rune 請考慮使用 類型:

  • 呼叫需要 Unicode 純量值的 API
  • 明確處理代理字組

需要 Unicode 純量值的 API

如果您的程式代碼char逐一查看 或ReadOnlySpan<char>中的string實例,某些char方法將無法在代理範圍中的實例上正確char運作。 例如,下列 API 需要純量值 char 才能正常運作:

下列範例顯示如果任一實例是代理程式代碼點, char 將無法正確運作的程式代碼:

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

以下是適用於 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;
}

上述程式代碼適用於某些語言,例如英文:

CountLettersInString("Hello")
// Returns 5

但它不適用於基本多語平面以外的語言,例如 Osage:

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

此方法傳回 Osage 文字 char 不正確結果的原因是 Osage 字母的實例是 Surrogate 字碼點。 沒有單一 Surrogate 字碼點有足夠的信息來判斷其是否為字母。

如果您將此程式碼變更為使用 Runechar而不是 ,此方法會正確搭配基本多語平面以外的程式代碼點運作:

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

以下是適用於 ReadOnlySpan<char>的對等程式代碼:

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

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

    return letterCount;
}

上述程式代碼會正確計算 Osage 字母:

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

明確處理代理字組的程序代碼

如果您的程式代碼呼叫明確在代理程式代碼點上操作的 API,請考慮使用 Rune 類型,例如下列方法:

例如,下列方法具有處理 Surrogate 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.");
        }
    }
}

如果這類程式代碼使用 Rune,則比較簡單,如下列範例所示:

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

不要使用 Rune 的時機

如果您的程式代碼, 您不需要使用 Rune 類型:

  • 尋找完全 char 相符專案
  • 在已知的 char 值上分割字串

如果您的程式 Rune 代碼, 使用 類型可能會傳回不正確的結果:

  • 計算中的顯示字元數目 string

尋找完全 char 相符專案

下列程式代碼會逐一 string 查看尋找特定字元,並傳回第一個相符專案的索引。 不需要變更此程式代碼來使用 Rune,因為程式代碼正在尋找以單 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
}

在已知的上分割字串 char

呼叫和使用分隔符很常見 string.Split ,例如 ' ' (空格) 或 ',' (逗號),如下列範例所示:

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

這裡不需要使用 Rune ,因為程式代碼正在尋找以單 char一 表示的字元。

計算中的顯示字元數目 string

字串中的實例數目 Rune 可能不符合顯示字串時所顯示的使用者感知字元數目。

由於 Rune 實例代表 Unicode 純量值,因此遵循 Unicode 文字分割指導方針的 元件可用來 Rune 做為計算顯示字元的建置元件。

StringInfo 類型可用來計算顯示字元,但在 .NET 5+ 以外的所有案例中,它不會正確計算。

如需詳細資訊,請參閱 Grapheme 叢集

如何具現化 Rune

有數種方式可以取得 Rune 實例。 您可以使用建構函式直接從下列專案建立 Rune

  • 程式代碼點。

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

    Rune c = new Rune('a');
    
  • 代理 char 字組。

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

如果輸入不代表有效的 Unicode 純量值,則所有建構函式都會擲回 ArgumentException

呼叫端有一個 Rune.TryCreate 方法,他們不希望在失敗時擲回例外狀況。

Rune 實例也可以從現有的輸入序列讀取。 例如,假設有 ReadOnlySpan<char> 代表UTF-16數據的 ,此方法 Rune.DecodeFromUtf16 會在輸入範圍開頭傳回第一個 Rune 實例。 方法 Rune.DecodeFromUtf8 的運作方式類似,接受 ReadOnlySpan<byte> 代表UTF-8數據的參數。 有對等的方法可從範圍結尾讀取,而不是範圍開頭。

的查詢屬性 Rune

若要取得 實例的 Rune 整數代碼點值,請使用 Rune.Value 屬性。

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

類型上 char 可用的許多靜態 API 也可用於 Rune 類型。 例如, Rune.IsWhiteSpaceRune.GetUnicodeCategory 相當於 Char.IsWhiteSpaceChar.GetUnicodeCategory 方法。 Rune方法可正確處理 Surrogate 字組。

下列範例程式代碼會接受 ReadOnlySpan<char> 做為輸入,並從範圍開頭和結尾 Rune 修剪每個不是字母或數字的結尾。

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

Rune之間char有一些 API 差異。 例如:

Rune將轉換為UTF-8或UTF-16

Rune由於 是 Unicode 純量值,因此可以轉換成 UTF-8、UTF-16 或 UTF-32 編碼。 此 Rune 類型內建支持轉換成UTF-8和UTF-16。

會將 Rune.EncodeToUtf16Rune 實例 char 轉換成 實例。 若要查詢將實例轉換成 Rune UTF-16所產生的實例數目char,請使用 Rune.Utf16SequenceLength 屬性。 UTF-8 轉換也有類似的方法。

下列範例會將 Rune 實例 char 轉換成陣列。 程式代碼假設您在變數中有 rune 實例Rune

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

string由於 是UTF-16字元序列,因此下列範例也會將 實例轉換成 Rune UTF-16:

string theString = rune.ToString();

下列範例會將 Rune 實體 UTF-8 轉換成位元組數組:

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

Rune.EncodeToUtf16Rune.EncodeToUtf8 方法會傳回寫入之項目的實際數目。 如果目的地緩衝區太短而無法包含結果,它們就會擲回例外狀況。 對於想要避免例外狀況的呼叫端,也有非擲回 TryEncodeToUtf8TryEncodeToUtf16 方法。

.NET 中的 Rune 與其他語言

“rune” 一詞未定義於 Unicode 標準中。 此詞彙可追溯到 UTF-8的建立。 羅布·派克和肯·湯普森正在尋找一個詞彙,以描述最終會被稱為代碼點的內容。 他們解決了“rune”一詞,羅布·派克後來對Go程式設計語言的影響有助於普及這個詞。

不過,.NET Rune 類型與 Go rune 類型不相等。 在 Go 中,類型 runeint32別名。 Go rune 的目的是要代表 Unicode 字碼點,但它可以是任何 32 位值,包括代理字碼點和不是合法 Unicode 字碼點的值。

如需其他程式設計語言中的類似類型,請參閱 Rust 的基本 char 類型Swift Unicode.Scalar 的類型,這兩者都代表 Unicode 純量值。 它們提供類似的功能。NET 的類型 Rune ,且不允許具現化非合法 Unicode 純量值的值。