Kodowanie znaków na platformie .NET

Ten artykuł zawiera wprowadzenie do charsystemów kodowania acter używanych przez platformę .NET. W tym artykule wyjaśniono, jak Stringtypy , CharRune, i StringInfo działają z kodami Unicode, UTF-16 i UTF-8.

Termin character jest używany tutaj w ogólnym sensie tego , co czytelnik postrzega jako pojedynczy element wyświetlania. Typowe przykłady to litera "a", symbol "@" i emoji "🐂". Czasami to, co wygląda jak jeden charakter, składa się z wielu niezależnych elementów wyświetlania, jak wyjaśniono w sekcji w klastrach grafeme.

Typy string i char

Wystąpienie string klasy reprezentuje jakiś tekst. Element A string jest logicznie sekwencją 16-bitowych wartości, z których każda jest wystąpieniem char struktury. Element string. Właściwość Length zwraca liczbę char wystąpień w wystąpieniu string .

Następująca przykładowa funkcja wyświetla wartości w notacji szesnastkowej wszystkich char wystąpień w obiekcie string:

void PrintChars(string s)
{
    Console.WriteLine($"\"{s}\".Length = {s.Length}");
    for (int i = 0; i < s.Length; i++)
    {
        Console.WriteLine($"s[{i}] = '{s[i]}' ('\\u{(int)s[i]:x4}')");
    }
    Console.WriteLine();
}

string Przekaż komunikat "Hello" do tej funkcji i uzyskasz następujące dane wyjściowe:

PrintChars("Hello");
"Hello".Length = 5
s[0] = 'H' ('\u0048')
s[1] = 'e' ('\u0065')
s[2] = 'l' ('\u006c')
s[3] = 'l' ('\u006c')
s[4] = 'o' ('\u006f')

Każdy charaktuer jest reprezentowany przez jedną char wartość. Ten wzorzec ma wartość true dla większości języków na świecie. Na przykład poniżej przedstawiono dane wyjściowe dla dwóch chińskich charaktualizatorów, które brzmią jak nǐ hǎo i oznaczają hello:

PrintChars("你好");
"你好".Length = 2
s[0] = '你' ('\u4f60')
s[1] = '好' ('\u597d')

Jednak w przypadku niektórych języków i niektórych symboli i emoji potrzeba dwóch char wystąpień reprezentujących pojedynczy charelement acter. Porównaj na przykład charosoby działające i char wystąpienia w słowie, co oznacza Osage w języku Osage:

PrintChars("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟");
"𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟".Length = 17
s[0] = '�' ('\ud801')
s[1] = '�' ('\udccf')
s[2] = '�' ('\ud801')
s[3] = '�' ('\udcd8')
s[4] = '�' ('\ud801')
s[5] = '�' ('\udcfb')
s[6] = '�' ('\ud801')
s[7] = '�' ('\udcd8')
s[8] = '�' ('\ud801')
s[9] = '�' ('\udcfb')
s[10] = '�' ('\ud801')
s[11] = '�' ('\udcdf')
s[12] = ' ' ('\u0020')
s[13] = '�' ('\ud801')
s[14] = '�' ('\udcbb')
s[15] = '�' ('\ud801')
s[16] = '�' ('\udcdf')

W poprzednim przykładzie każdy charaktuer z wyjątkiem miejsca jest reprezentowany przez dwa char wystąpienia.

Pojedynczy emoji Unicode jest również reprezentowany przez dwa chars, jak pokazano w poniższym przykładzie przedstawiającym emoji x:

"🐂".Length = 2
s[0] = '�' ('\ud83d')
s[1] = '�' ('\udc02')

Te przykłady pokazują, że wartość string.Length, która wskazuje liczbę char wystąpień, niekoniecznie wskazuje liczbę wyświetlanych charelementów acters. Pojedyncze char wystąpienie samo w sobie nie musi reprezentować aktuatora char.

Pary char , które są mapowane na pojedynczy charelement acter, są nazywane parami zastępczymi. Aby zrozumieć, jak działają, musisz zrozumieć kodowanie Unicode i UTF-16.

Punkty kodu Unicode

Unicode to międzynarodowy standard kodowania używany na różnych platformach oraz w różnych językach i skryptach.

Standard Unicode definiuje ponad 1,1 miliona punktów kodu. Punkt kodu to wartość całkowita, która może wahać się od 0 do U+10FFFF (liczba dziesiętna 1114 1111). Niektóre punkty kodu są przypisywane do liter, symboli lub emoji. Inne są przypisywane do akcji, które kontrolują sposób wyświetlania tekstu lub chardziałania, takie jak przejście do nowego wiersza. Wiele punktów kodu nie jest jeszcze przypisanych.

Oto kilka przykładów przypisań punktów kodu z linkami do ts Unicode char, w których są wyświetlane:

Dziesiętne Hex Przykład opis
10 U+000A Nie dotyczy KANAŁ LINIOWY
97 U+0061 a MAŁA LITERA A (ALFABET ŁACIŃSKI)
562 U+0232 Ȳ WIELKA LITERA Y Z MACRONEM
68,675 U+10C43 𐱃 STARY TURECKI LIST ORKHON W
127,801 U+1F339 🌹 Emoji ROSE

Punkty kodu są niestandardowie określane przy użyciu składni U+xxxx, gdzie xxxx jest wartością całkowitą zakodowaną w formacie szesnastkowym.

W obrębie pełnego zakresu punktów kodu znajdują się dwa podzestawy:

  • Podstawowa wielojęzyczna płaszczyzna (BMP) w zakresie U+0000..U+FFFF. Ten 16-bitowy zakres zapewnia 65 536 punktów kodu na tyle, aby pokryć większość systemów pisania na świecie.
  • Dodatkowe punkty kodu w zakresie U+10000..U+10FFFF. Ten 21-bitowy zakres udostępnia ponad milion dodatkowych punktów kodu, które mogą być używane w mniej znanych językach i innych celach, takich jak emoji.

Na poniższym diagramie przedstawiono relację między BMP a dodatkowymi punktami kodu.

Punkty kodu BMP i dodatkowe

Jednostki kodu UTF-16

16-bitowy format transformacji Unicode (UTF-16) to charsystem kodowania aktuatora, który używa 16-bitowych jednostek kodu do reprezentowania punktów kodu Unicode. Platforma .NET używa formatu UTF-16 do kodowania tekstu w obiekcie string. Wystąpienie char reprezentuje 16-bitową jednostkę kodu.

Jedna 16-bitowa jednostka kodu może reprezentować dowolny punkt kodu w 16-bitowym zakresie podstawowej płaszczyzny wielojęzycznej. Jednak w przypadku punktu kodu w zakresie pomocniczym potrzebne są dwa char wystąpienia.

Pary zastępcze

Tłumaczenie dwóch wartości 16-bitowych na pojedynczą wartość 21-bitową jest obsługiwane przez specjalny zakres nazywany punktami kodu zastępczego z U+D800 do U+DFFF (dziesiętne od 55 296 do 57 343), włącznie.

Na poniższym diagramie przedstawiono relację między BMP a zastępczymi punktami kodu.

Punkty kodu BMP i zastępcze

Gdy punkt kodu zastępczego (U+D800..U+DBFF) jest natychmiast obserwowany przez niski punkt kodu zastępczego (U+DC00..U+DFFF), para jest interpretowana jako dodatkowy punkt kodu przy użyciu następującej formuły:

code point = 0x10000 +
  ((high surrogate code point - 0xD800) * 0x0400) +
  (low surrogate code point - 0xDC00)

Oto ta sama formuła używająca notacji dziesiętnej:

code point = 65,536 +
  ((high surrogate code point - 55,296) * 1,024) +
  (low surrogate code point - 56,320)

Wysoki punkt kodu zastępczego nie ma większej wartości liczbowej niż niski punkt kodu zastępczego. Wysoki punkt kodu zastępczego jest nazywany "wysokim", ponieważ służy do obliczania 10-bitowych 10 bitów wyższej kolejności zakresu punktów kodu 20-bitowego. Niski punkt kodu zastępczego służy do obliczania 10 bitów niższej kolejności.

Na przykład rzeczywisty punkt kodu odpowiadający parze 0xD83C zastępczej i 0xDF39 jest obliczany w następujący sposób:

actual = 0x10000 + ((0xD83C - 0xD800) * 0x0400) + (0xDF39 - 0xDC00)
       = 0x10000 + (          0x003C  * 0x0400) +           0x0339
       = 0x10000 +                      0xF000  +           0x0339
       = 0x1F339

Oto to samo obliczenie przy użyciu notacji dziesiętnej:

actual =  65,536 + ((55,356 - 55,296) * 1,024) + (57,145 - 56320)
       =  65,536 + (              60  * 1,024) +             825
       =  65,536 +                     61,440  +             825
       = 127,801

W poprzednim przykładzie pokazano, że "\ud83c\udf39" kodowanie U+1F339 ROSE ('🌹') UTF-16 punktu kodu wymienionego wcześniej.

Wartości skalarne Unicode

Termin Wartość skalarna Unicode odnosi się do wszystkich punktów kodu innych niż punkty kodu zastępczego. Innymi słowy, wartość skalarna to dowolny punkt kodu, który jest przypisany do aktuatora lub może zostać przypisany charchardo aktuatora w przyszłości. "Znak" w tym miejscu odnosi się do wszystkich elementów, które można przypisać do punktu kodu, co obejmuje takie elementy jak akcje kontrolujące sposób wyświetlania tekstu lub charelementów akcji.

Na poniższym diagramie przedstawiono punkty kodu wartości skalarnych.

Wartości skalarne

Typ Rune jako wartość skalarna

Począwszy od platformy .NET Core 3.0, System.Text.Rune typ reprezentuje wartość skalarną Unicode. Rune nie jest dostępny w programie .NET Core 2.x lub .NET Framework 4.x.

Konstruktory Rune sprawdzają, czy wynikowe wystąpienie jest prawidłową wartością skalarną Unicode, w przeciwnym razie zgłaszają wyjątek. W poniższym przykładzie pokazano kod, który pomyślnie tworzy Rune wystąpienia wystąpień, ponieważ dane wejściowe reprezentują prawidłowe wartości skalarne:

Rune a = new Rune('a');
Rune b = new Rune(0x0061);
Rune c = new Rune('\u0061');
Rune d = new Rune(0x10421);
Rune e = new Rune('\ud801', '\udc21');

Poniższy przykład zgłasza wyjątek, ponieważ punkt kodu znajduje się w zakresie zastępczym i nie jest częścią pary zastępczej:

Rune f = new Rune('\ud801');

Poniższy przykład zgłasza wyjątek, ponieważ punkt kodu wykracza poza zakres dodatkowy:

Rune g = new Rune(0x12345678);

Rune przykład użycia: zmiana wielkości liter

Interfejs API, który przyjmuje char element i zakłada, że działa z punktem kodu, który jest wartością skalarną, nie działa poprawnie, jeśli char element pochodzi z pary zastępczej. Rozważmy na przykład następującą metodę, która wywołuje Char.ToUpperInvariant każdą char metodę w obiekcie string:

// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
static string ConvertToUpperBadExample(string input)
{
    StringBuilder builder = new StringBuilder(input.Length);
    for (int i = 0; i < input.Length; i++) /* or 'foreach' */
    {
        builder.Append(char.ToUpperInvariant(input[i]));
    }
    return builder.ToString();
}

Jeśli element inputstring zawiera małą literę er Deseret (𐑉), ten kod nie zostanie przekonwertowany na wielkie litery (𐐡). Kod wywołuje char.ToUpperInvariant oddzielnie w każdym zastępczym punkcie U+D801 kodu i U+DC49. Ale U+D801 sama nie ma wystarczającej ilości informacji, aby zidentyfikować ją jako małą literę, więc char.ToUpperInvariant pozostawia ją samodzielnie. I obsługuje U+DC49 to w ten sam sposób. Wynikiem jest to, że małe litery "𐑉" w obiekcie inputstring nie są konwertowane na wielkie litery "𐑉".

Poniżej przedstawiono dwie opcje poprawnego konwertowania elementu string na wielkie litery:

  • Wywołaj String.ToUpperInvariant dane wejściowe string zamiast iterować char-by-char. Metoda string.ToUpperInvariant ma dostęp do obu części każdej pary zastępczej, dzięki czemu może poprawnie obsłużyć wszystkie punkty kodu Unicode.

  • Iteruj wartości skalarne Unicode jako Rune wystąpienia zamiast char wystąpień, jak pokazano w poniższym przykładzie. Rune Ponieważ wystąpienie jest prawidłową wartością skalarną Unicode, można ją przekazać do interfejsów API, które oczekują działania na wartości skalarnej. Na przykład wywołanie Rune.ToUpperInvariant metody , jak pokazano w poniższym przykładzie, daje poprawne wyniki:

    static string ConvertToUpper(string input)
    {
        StringBuilder builder = new StringBuilder(input.Length);
        foreach (Rune rune in input.EnumerateRunes())
        {
            builder.Append(Rune.ToUpperInvariant(rune));
        }
        return builder.ToString();
    }
    

Inne Rune interfejsy API

Typ Rune uwidacznia analogię wielu char interfejsów API. Na przykład następujące metody dubluje statyczne interfejsy API w typie char :

Aby uzyskać nieprzetworzone wartości skalarne z Rune wystąpienia, użyj Rune.Value właściwości .

Aby przekonwertować Rune wystąpienie z powrotem na sekwencję chars, użyj Rune.ToString metody lub Rune.EncodeToUtf16 .

Ponieważ dowolna wartość skalarna Unicode jest reprezentowana przez jedną char lub przez parę zastępczą, każde Rune wystąpienie może być reprezentowane przez co najwyżej 2 char wystąpienia. Użyj Rune.Utf16SequenceLength polecenia , aby zobaczyć, ile char wystąpień jest wymaganych do reprezentowania Rune wystąpienia.

Aby uzyskać więcej informacji na temat typu .NETRune, zobacz dokumentację interfejsu Rune API.

Klastry Grapheme

To, co wygląda jak jeden character może wynikać z kombinacji wielu punktów kodu, więc bardziej opisowy termin, który jest często używany zamiast "character" jest klaster grapheme. Równoważny termin na platformie .NET jest elementem tekstowym.

string Rozważ wystąpienia "a", "á", "á" i "👩🏽‍🚒". Jeśli system operacyjny obsługuje je zgodnie ze standardem Unicode, każde z tych string wystąpień jest wyświetlane jako pojedynczy element tekstowy lub klaster grapheme. Jednak ostatnie dwa są reprezentowane przez więcej niż jeden punkt kodu wartości skalarnej.

  • Wartość string "a" jest reprezentowana przez jedną wartość skalarną i zawiera jedno char wystąpienie.

    • U+0061 LATIN SMALL LETTER A
  • Wartość string "á" jest reprezentowana przez jedną wartość skalarną i zawiera jedno char wystąpienie.

    • U+00E1 LATIN SMALL LETTER A WITH ACUTE
  • Wyrażenie string "á" wygląda tak samo jak "á", ale jest reprezentowane przez dwie wartości skalarne i zawiera dwa char wystąpienia.

    • U+0061 LATIN SMALL LETTER A
    • U+0301 COMBINING ACUTE ACCENT
  • Na koniec element string "👩🏽‍🚒" jest reprezentowany przez cztery wartości skalarne i zawiera siedem char wystąpień.

    • U+1F469 WOMAN (zakres dodatkowy, wymaga pary zastępczej)
    • U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4 (zakres dodatkowy, wymaga pary zastępczej)
    • U+200D ZERO WIDTH JOINER
    • U+1F692 FIRE ENGINE (zakres dodatkowy, wymaga pary zastępczej)

W niektórych z powyższych przykładów — takich jak łączący modyfikator akcentu lub modyfikator tonu skóry — punkt kodu nie jest wyświetlany jako autonomiczny element na ekranie. Zamiast tego służy do modyfikowania wyglądu elementu tekstowego, który przyszedł przed nim. Te przykłady pokazują, że może upłynąć wiele wartości skalarnych, aby utworzyć to, co uważamy za pojedyncze "character" lub "grapheme cluster".

Aby wyliczyć klastry stringgrafu klasy , użyj StringInfo klasy , jak pokazano w poniższym przykładzie. Jeśli znasz język Swift, typ platformy .NET StringInfo jest koncepcyjnie podobny do typu swiftcharacter.

Przykład: liczba charwystąpień elementów , Runei tekstowych

W interfejsach API platformy .NET klaster grapheme jest nazywany elementem tekstowym. Poniższa metoda demonstruje różnice między wystąpieniami charelementów , i Rune, a elementami tekstowymi w obiekcie string:

static void PrintTextElementCount(string s)
{
    Console.WriteLine(s);
    Console.WriteLine($"Number of chars: {s.Length}");
    Console.WriteLine($"Number of runes: {s.EnumerateRunes().Count()}");

    TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(s);

    int textElementCount = 0;
    while (enumerator.MoveNext())
    {
        textElementCount++;
    }

    Console.WriteLine($"Number of text elements: {textElementCount}");
}
PrintTextElementCount("a");
// Number of chars: 1
// Number of runes: 1
// Number of text elements: 1

PrintTextElementCount("á");
// Number of chars: 2
// Number of runes: 2
// Number of text elements: 1

PrintTextElementCount("👩🏽‍🚒");
// Number of chars: 7
// Number of runes: 4
// Number of text elements: 1

Jeśli uruchomisz ten kod w programie .NET Framework lub .NET Core 3.1 lub starszym, liczba elementów tekstowych dla emoji będzie zawierać wartość 4. Jest to spowodowane usterką w StringInfo klasie, która została usunięta na platformie .NET 5.

Przykład: dzielenie string wystąpień

Podczas dzielenia string wystąpień unikaj dzielenia par zastępczych i klastrów grafeme. Rozważmy następujący przykład nieprawidłowego kodu, który zamierza wstawić podziały wierszy co 10 charelementów acters w obiekcie string:

// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
static string InsertNewlinesEveryTencharsBadExample(string input)
{
    StringBuilder builder = new StringBuilder();

    // First, append chunks in multiples of 10 chars
    // followed by a newline.
    int i = 0;
    for (; i < input.Length - 10; i += 10)
    {
        builder.Append(input, i, 10);
        builder.AppendLine(); // newline
    }

    // Then append any leftover data followed by
    // a final newline.
    builder.Append(input, i, input.Length - i);
    builder.AppendLine(); // newline

    return builder.ToString();
}

Ponieważ ten kod wylicza char wystąpienia, para zastępcza, która ma się zdarzyć, wciągnie granicę 10-char zostanie podzielona i nowa linia wstrzykiwana między nimi. Ta wstawienie wprowadza uszkodzenie danych, ponieważ zastępcze punkty kodu mają znaczenie tylko jako pary.

Możliwość uszkodzenia danych nie jest wyeliminowana, jeśli wyliczasz Rune wystąpienia (wartości skalarne) zamiast char wystąpień. Zestaw Rune wystąpień może składać się z klastra grafeme, który łączy granicę 10-char . Jeśli zestaw klastra grapheme został podzielony, nie można go poprawnie zinterpretować.

Lepszym rozwiązaniem jest podzielenie string wartości przez zliczanie klastrów grafu lub elementów tekstowych, jak w poniższym przykładzie:

static string InsertNewlinesEveryTenTextElements(string input)
{
    StringBuilder builder = new StringBuilder();

    // Append chunks in multiples of 10 chars

    TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(input);

    int textElementCount = 1;
    while (enumerator.MoveNext())
    {
        builder.Append(enumerator.Current);
        if (textElementCount % 10 == 0 && textElementCount > 0)
        {
            builder.AppendLine(); // newline
        }
        textElementCount++;
    }

    // Add a final newline.
    builder.AppendLine(); // newline
    return builder.ToString();

}

Jak wspomniano wcześniej, przed platformą .NET 5 StringInfo klasa miała usterkę powodującą nieprawidłowe obsługę niektórych klastrów grafeme.

UTF-8 i UTF-32

Poprzednie sekcje koncentrowały się na architekturze UTF-16, ponieważ jest to używane przez platformę .NET do kodowania string wystąpień. Istnieją inne systemy kodowania Unicode — UTF-8 i UTF-32. Te kodowania używają odpowiednio 8-bitowych jednostek kodu i 32-bitowych jednostek kodu.

Podobnie jak UTF-16, kod UTF-8 wymaga wielu jednostek kodu do reprezentowania niektórych wartości skalarnych Unicode. UtF-32 może reprezentować dowolną wartość skalarną w jednej 32-bitowej jednostce kodu.

Poniżej przedstawiono kilka przykładów pokazujących, jak ten sam punkt kodu Unicode jest reprezentowany w każdym z tych trzech systemów kodowania Unicode:

Scalar: U+0061 LATIN SMALL LETTER A ('a')
UTF-8 : [ 61 ]           (1x  8-bit code unit  = 8 bits total)
UTF-16: [ 0061 ]         (1x 16-bit code unit  = 16 bits total)
UTF-32: [ 00000061 ]     (1x 32-bit code unit  = 32 bits total)

Scalar: U+0429 CYRILLIC CAPITAL LETTER SHCHA ('Щ')
UTF-8 : [ D0 A9 ]        (2x  8-bit code units = 16 bits total)
UTF-16: [ 0429 ]         (1x 16-bit code unit  = 16 bits total)
UTF-32: [ 00000429 ]     (1x 32-bit code unit  = 32 bits total)

Scalar: U+A992 JAVANESE LETTER GA ('ꦒ')
UTF-8 : [ EA A6 92 ]     (3x  8-bit code units = 24 bits total)
UTF-16: [ A992 ]         (1x 16-bit code unit  = 16 bits total)
UTF-32: [ 0000A992 ]     (1x 32-bit code unit  = 32 bits total)

Scalar: U+104CC OSAGE CAPITAL LETTER TSHA ('𐓌')
UTF-8 : [ F0 90 93 8C ]  (4x  8-bit code units = 32 bits total)
UTF-16: [ D801 DCCC ]    (2x 16-bit code units = 32 bits total)
UTF-32: [ 000104CC ]     (1x 32-bit code unit  = 32 bits total)

Jak wspomniano wcześniej, pojedyncza jednostka kodu UTF-16 z pary zastępczej jest bez znaczenia sama w sobie. W ten sam sposób pojedyncza jednostka kodu UTF-8 jest bez znaczenia, jeśli znajduje się w sekwencji dwóch, trzech lub czterech używanych do obliczenia wartości skalarnej.

Uwaga

Począwszy od języka C# 11, można reprezentować literały UTF-8 string przy użyciu sufiksu "u8" na literału string. Aby uzyskać więcej informacji na temat literałów UTF-8 string , zobacz sekcję "string literały" artykułu na temat wbudowanych typów odwołań w przewodniku języka C#.

Endianness

Na platformie .NET jednostki string kodu UTF-16 obiektu są przechowywane w ciągłej pamięci jako sekwencja 16-bitowych liczb całkowitych (char wystąpień). Bity poszczególnych jednostek kodu są określone zgodnie z endianness bieżącej architektury.

W małej architekturze string endian, składające się z punktów [ D801 DCCC ] kodu UTF-16 zostaną określone w pamięci jako bajty [ 0x01, 0xD8, 0xCC, 0xDC ]. W architekturze big-endian, która byłaby taka sama string w pamięci co bajty [ 0xD8, 0x01, 0xDC, 0xCC ].

Systemy komputerowe komunikujące się ze sobą muszą uzgodnić reprezentację danych przekraczających przewód. Większość protokołów sieciowych używa standardu UTF-8 podczas przesyłania tekstu, częściowo w celu uniknięcia problemów, które mogą wynikać z komunikacji dużej maszyny endian z małą maszyną endian. Składający string się z punktów [ F0 90 93 8C ] kodu UTF-8 będzie zawsze reprezentowany jako bajty [ 0xF0, 0x90, 0x93, 0x8C ] niezależnie od endianness.

Aby używać formatu UTF-8 do przesyłania tekstu, aplikacje platformy .NET często używają kodu takiego jak w poniższym przykładzie:

string stringToWrite = GetString();
byte[] stringAsUtf8Bytes = Encoding.UTF8.GetBytes(stringToWrite);
await outputStream.WriteAsync(stringAsUtf8Bytes, 0, stringAsUtf8Bytes.Length);

W poprzednim przykładzie metoda Encoding.UTF8.GetBytes dekoduje kodowanie UTF-16 string z powrotem do serii wartości skalarnych Unicode, a następnie ponownie koduje te wartości skalarne do UTF-8 i umieszcza wynikową sekwencję w tablicybyte. Metoda Encoding.UTF8.GetString wykonuje odwrotną transformację, konwertując tablicę UTF-8 byte na utF-16 string.

Ostrzeżenie

Ponieważ utF-8 jest powszechne w Internecie, może być kuszące odczytywanie nieprzetworzonych bajtów z przewodu i traktowanie danych tak, jakby były to UTF-8. Należy jednak sprawdzić, czy jest rzeczywiście dobrze sformułowany. Złośliwy klient może przesłać do usługi źle sformułowany kod UTF-8. Jeśli korzystasz z tych danych tak, jakby były prawidłowo sformułowane, może to spowodować błędy lub luki w zabezpieczeniach w aplikacji. Aby zweryfikować dane UTF-8, można użyć metody takiej jak Encoding.UTF8.GetString, która przeprowadzi walidację podczas konwertowania danych przychodzących na string.

Poprawnie sformułowane kodowanie

Poprawnie sformułowane kodowanie Unicode to string jednostka kodu, która może być dekodowana jednoznacznie i bez błędów w sekwencji wartości skalarnych Unicode. Dobrze sformułowane dane mogą być transkodowane swobodnie między UTF-8, UTF-16 i UTF-32.

Pytanie, czy sekwencja kodowania jest prawidłowo sformułowana, czy nie jest niepowiązana z endiannessem architektury maszyny. Źle sformułowana sekwencja UTF-8 jest źle sformułowana w ten sam sposób zarówno na maszynach big-endian i little-endian.

Oto kilka przykładów źle sformułowanych kodowań:

  • W formacie UTF-8 sekwencja [ 6C C2 61 ] jest źle sformułowana, ponieważ C2 nie można jej obserwować.61

  • W UTF-16 sekwencja [ DC00 DD00 ] (lub, w C#, string"\udc00\udd00") jest źle sformułowana, ponieważ niski zastępca DC00 nie może być obserwowany przez inny niski zastępca DD00.

  • W formacie UTF-32 sekwencja [ 0011ABCD ] jest źle sformułowana, ponieważ 0011ABCD znajduje się poza zakresem wartości skalarnych Unicode.

Na platformie .NET string wystąpienia prawie zawsze zawierają dobrze sformułowane dane UTF-16, ale nie są gwarantowane. W poniższych przykładach pokazano prawidłowy kod języka C#, który tworzy nieprawidłowo sformułowane dane UTF-16 w string wystąpieniach.

  • Źle sformułowany literał:

    const string s = "\ud800";
    
  • Substring dzielący parę zastępczą:

    string x = "\ud83e\udd70"; // "🥰"
    string y = x.Substring(1, 1); // "\udd70" standalone low surrogate
    

Interfejsy API, takie jak Encoding.UTF8.GetString nigdy, nie zwracają nieumyślnych string wystąpień. Encoding.GetString metody i Encoding.GetBytes wykrywają nieprawidłowo sformułowane sekwencje w danych wejściowych i wykonują charpodstawianie aktuału podczas generowania danych wyjściowych. Jeśli na przykład Encoding.ASCII.GetString(byte[]) w danych wejściowych zostanie wyświetlony bajt inny niż ASCII (poza zakresem U+0000.U+007F), wstawia znak "?"" do zwróconego string wystąpienia. Encoding.UTF8.GetString(byte[]) Zastępuje nieprawidłowo sformułowane sekwencje U+FFFD REPLACEMENT CHARACTER ('�') UTF-8 w zwróconym string wystąpieniu. Aby uzyskać więcej informacji, zobacz Standard Unicode, Sekcje 5.22 i 3.9.

Wbudowane Encoding klasy można również skonfigurować tak, aby zgłaszały wyjątek, a nie wykonywać charpodstawienia aktuału w przypadku wystąpienia źle sformułowanych sekwencji. Takie podejście jest często stosowane w aplikacjach z uwzględnieniem zabezpieczeń, w których charzastępowanie aktualizatora może nie być akceptowalne.

byte[] utf8Bytes = ReadFromNetwork();
UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
string asString = encoding.GetString(utf8Bytes); // will throw if 'utf8Bytes' is ill-formed

Aby uzyskać informacje na temat używania wbudowanych klas, zobacz How to use acter encoding classes in Encoding .NET (Jak używać charklas kodowania aktuatora na platformie .NET).

Zobacz też