Kodowanie znaków na platformie .NET
Ten artykuł zawiera wprowadzenie do systemów kodowania znaków 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.
Ten termin jest używany 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 znak, 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 znak 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 znaków, które brzmią jak nǐ hǎo i oznaczają wartość 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 znak. Porównaj na przykład znaki 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 znak z wyjątkiem spacji jest reprezentowany przez dwa char
wystąpienia.
Pojedynczy emoji Unicode jest również reprezentowany przez dwa char
s, 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 znaków. Pojedyncze char
wystąpienie samo w sobie nie musi reprezentować znaku.
Pary char
mapowane na pojedynczy znak 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 znaków, 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 wykresów Unicode, 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.
Jednostki kodu UTF-16
16-bitowy format przekształcenia Unicode (UTF-16) to system kodowania znaków, 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.
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 znaku lub może zostać przypisany znak w przyszłości. "Znak" w tym miejscu odnosi się do dowolnych elementów, które można przypisać do punktu kodu, w tym akcji, które kontrolują sposób wyświetlania tekstu lub znaków.
Na poniższym diagramie przedstawiono punkty kodu wartości skalarnych.
Typ Rune jako wartość skalarna
Ważne
Typ Rune
nie jest dostępny w programie .NET Framework.
Na platformie .NET System.Text.Rune typ reprezentuje wartość skalarną Unicode.
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 input
string 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 input
string 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
. Metodastring.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 zamiastchar
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ę char
s, 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 znak, może wynikać z kombinacji wielu punktów kodu, dlatego bardziej opisowy termin, który jest często używany zamiast "znaku" to klaster grafeme. 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 siedemchar
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 pojedynczy "znak" lub "klaster grafeme".
Aby wyliczyć klastry string
grafu 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 char
elementó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
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 znaków 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ępcaDC00
nie może być obserwowany przez inny niski zastępcaDD00
.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";
Podciąg 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ą podstawienie znaków 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ć pod kątem zgłaszania wyjątku, a nie podstawiania znaków, gdy występują źle sformułowane sekwencje. Takie podejście jest często stosowane w aplikacjach z uwzględnieniem zabezpieczeń, gdzie zastępowanie znaków 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 character encoding classes in Encoding
.NET (Jak używać klas kodowania znaków na platformie .NET).