Kódování znaků v rozhraní .NET
Tento článek poskytuje Úvod k char systémům kódování Acter, které používá .NET. Článek vysvětluje, jak String typy, Char , Rune a StringInfo fungují s kódováním Unicode, UTF-16 a UTF-8.
Pojem char Acter se zde používá v obecném smyslu, co čtenář detekuje jako jeden prvek zobrazení. Běžnými příklady jsou písmena "a", symbol "@" a Emoji " 🐂 ". V některých případech se zdá char , že jeden Acter se ve skutečnosti skládá z několika nezávislých prvků zobrazení, protože se vysvětluje oddíl grapheme clusterů .
stringTypy a char
Instance string třídy představuje nějaký text. stringJe logicky sekvencí 16bitových hodnot, z nichž každá je instancí char struktury. Rozhraní string . Vlastnost length vrátí počet char instancí string instance.
Následující ukázková funkce vytiskne hodnoty v šestnáctkovém zápisu všech char instancí v string :
Předat string "Hello" této funkci a získáte následující výstup:
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')
Jednotlivé char actery jsou reprezentovány jedinou char hodnotou. Tento model má pro většinu jazyků světa hodnotu true. Zde je například výstup pro dva čínské char actersy, jako je nǐ hǎo a střední hodnota Hello:
PrintChars("你好");
"你好".Length = 2
s[0] = '你' ('\u4f60')
s[1] = '好' ('\u597d')
U některých jazyků a pro některé symboly a Emoji ale převezme dvě instance, char aby představovaly jeden char Acter. Porovnejte například char acters a char instance ve slově, které znamenají Osage v Osage jazyce:
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')
V předchozím příkladu každý char Acter s výjimkou místa je reprezentován dvěma char instancemi.
Jedna Emoji Unicode je také zastoupena dvěma char s, jak je vidět v následujícím příkladu, který ukazuje Ox Emoji:
"🐂".Length = 2
s[0] = '�' ('\ud83d')
s[1] = '�' ('\udc02')
Tyto příklady ukazují, že hodnota string.Length , která označuje počet char instancí, nemusí nutně označovat počet zobrazených char acters. Jedna char instance sama o sobě nutně reprezentuje char Acter.
charPáry, které jsou mapovány na jeden char Acter, se nazývají náhradní páry. Pro pochopení, jak fungují, je nutné pochopit kódování Unicode a UTF-16.
Body kódu Unicode
Unicode je mezinárodní standard kódování pro použití na různých platformách a v různých jazycích a skriptech.
Standard Unicode definuje více než 1 100 000 kódových bodů. Bod kódu je celočíselná hodnota, která může být v rozsahu od 0 do U+10FFFF (desítková 1 114 111). Některé body kódu jsou přiřazeny k písmenům, symbolům nebo emoji. Jiné jsou přiřazeny k akcím, které řídí způsob char zobrazení textu nebo acters, jako je například přechod na nový řádek. Mnoho bodů kódu ještě není přiřazeno.
Tady jsou některé příklady přiřazení bodů kódu s odkazy na sadu Unicode char TS, ve kterých se zobrazují:
| Decimal | Soustavy | Příklad | Popis |
|---|---|---|---|
| 10 | U+000A |
– | ČÁROVÝ KANÁL |
| 97 | U+0061 |
pro | MALÉ PÍSMENO LATINKY A |
| 562 | U+0232 |
Ȳ | VELKÉ PÍSMENO LATINKY Y S POMLČKOU |
| 68 675 | U+10C43 |
𐱃 | STARÉ ORKHONOVÉ DOPISY V |
| 127 801 | U+1F339 |
🌹 | RŮŽE – Emoji |
Body kódu jsou obvykle označovány pomocí syntaxe U+xxxx , kde xxxx je celočíselně zakódovaná celočíselná hodnota.
V celém rozsahu kódových bodů existují dva podrozsahy:
- Základní vícejazyčné roviny (BMP) v rozsahu
U+0000..U+FFFF. Tento 16bitový rozsah poskytuje 65 536 kódových bodů, které jsou dostatečně k pokrytí většiny systémů pro psaní světa. - Doplňkové body kódu v rozsahu
U+10000..U+10FFFF. Tento 21. rozsah poskytuje více než milion dalších kódových bodů, které lze použít pro méně známé jazyky a jiné účely, jako je například emoji.
Následující diagram znázorňuje vztah mezi BMP a doplňkovými body kódu.
Jednotky kódu UTF-16
16bitový formát transformace Unicode (UTF-16) je char systém kódování Acter, který používá 16bitové jednotky kódu pro reprezentaci bodů kódu Unicode. Rozhraní .NET používá UTF-16 ke kódování textu v string . charInstance představuje 16bitové jednotky kódu.
Jedna 16bitová jednotka kódu může představovat libovolný bod kódu v 16bitovém rozsahu základní vícejazyčné roviny. Ale pro bod kódu v doplňkovém rozsahu char jsou potřeba dvě instance.
Náhradní páry
Překlad 2 16 hodnot na jednu hodnotu na 21 bitů usnadňuje speciální rozsah nazvaný body náhrady, od U+D800 do U+DFFF (desítková 55 296 až 57 343) včetně.
Následující diagram znázorňuje vztah mezi BMP a náhradními body kódu.
Pokud je za klíčovým bodem vysoké náhrady U+D800..U+DBFF okamžitě následován bod nízkého náhrady ( U+DC00..U+DFFF ), je pár interpretován jako doplňkový bod kódu pomocí následujícího vzorce:
code point = 0x10000 +
((high surrogate code point - 0xD800) * 0x0400) +
(low surrogate code point - 0xDC00)
Toto je stejný vzorec pomocí desítkového zápisu:
code point = 65,536 +
((high surrogate code point - 55,296) * 1,024) +
(low surrogate code point - 56,320)
Vysoký náhradní bod kódu nemá vyšší číselnou hodnotu než bod nedostatku náhradního kódu. Vysoký náhradní bod kódu se nazývá "vysoká", protože se používá k výpočtu vyššího počtu 10 bitů v rozsahu bodu kódu. Nízká náhrada za bod kódu se používá k výpočtu 10 bitů s nižším pořadím.
Například skutečný bod kódu, který odpovídá náhradnímu páru 0xD83C a 0xDF39 je vypočítán následujícím způsobem:
actual = 0x10000 + ((0xD83C - 0xD800) * 0x0400) + (0xDF39 - 0xDC00)
= 0x10000 + ( 0x003C * 0x0400) + 0x0339
= 0x10000 + 0xF000 + 0x0339
= 0x1F339
Toto je stejný výpočet pomocí desítkového zápisu:
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
Předchozí příklad ukazuje, že "\ud83c\udf39" je kódování UTF-16 výše U+1F339 ROSE ('🌹') zmíněného bodu kódu.
Skalární hodnoty Unicode
Pojem skalární hodnota Unicode odkazuje na všechny body kódu, než jsou body náhradního kódu. Jinými slovy, skalární hodnota je jakýkoliv bod kódu, který je přiřazen char Acter nebo může být char v budoucnu přiřazena Acter. "Znak" tady odkazuje na cokoli, co může být přiřazeno k bodu kódu, který obsahuje takové věci jako akce, které řídí způsob char zobrazení textu nebo acters.
Následující diagram znázorňuje body kódu skalární hodnoty.
RuneTyp jako skalární hodnota
Počínaje rozhraním .NET Core 3,0 System.Text.Rune Typ představuje skalární hodnotu Unicode. Runenení k dispozici v rozhraní .net Core 2. x nebo .NET Framework 4. x.
RuneKonstruktory ověřují, zda je výsledná instance platnou skalární hodnotou kódování Unicode, jinak vyvolá výjimku. Následující příklad ukazuje kód, který úspěšně vytvoří instanci Rune instance, protože vstup představuje platné skalární hodnoty:
Následující příklad vyvolá výjimku, protože bod kódu je v rozsahu nahrazení a není součástí náhradního páru:
Následující příklad vyvolá výjimku, protože bod kódu je mimo doplňkový rozsah:
Rune Příklad použití: Změna malých písmen
Rozhraní API, které přijímá char a předpokládá, že pracuje s bodem kódu, který je skalární hodnota, nefunguje správně, pokud char je z náhradního páru. Zvažte například následující metodu, která volá Char.ToUpperInvariant každou char v string :
Pokud input string obsahuje písmeno Deseret s malým písmenem er ( 𐑉 ), tento kód ho nepřevede na velká písmena ( 𐐡 ). Kód volá char.ToUpperInvariant samostatně v každém náhradním bodě kódu a U+D801 U+DC49 . Sám o sobě ale nemá dostatek informací k tomu, aby je identifikoval jako malé písmeno, takže je U+D801 char.ToUpperInvariant ponechá sám. A zpracovává U+DC49 to stejným způsobem. Výsledkem je, že malá písmena 𐑉 v se nevedou převést na velká písmena input string "𐐡".
Tady jsou dvě možnosti pro správný převod na string velká písmena:
Místo String.ToUpperInvariant string iterace
char-by- volejte na vstupuchar. Metodastring.ToUpperInvariantmá přístup k oběma částem každého náhradního páru, takže dokáže správně zpracovat všechny body kódu Unicode.Iterovat skalárními hodnotami Unicode jako
Runeinstancemi místo instancí, jakcharje znázorněno v následujícím příkladu. Vzhledem k tomu, že instance je platná skalární hodnota Unicode, může být předána rozhraním API, která očekávají provoz seRuneskalární hodnotou. Například volání , Rune.ToUpperInvariant jak je znázorněno v následujícím příkladu, poskytuje správné výsledky:
Další Rune rozhraní API
Typ Rune zveřejňuje analogii mnoha rozhraní char API. Například následující metody zrcadlí statická rozhraní API char typu:
K získání nezpracované skalární hodnoty Rune z instance použijte vlastnost Rune.Value .
Pokud chcete Rune převést instanci zpět na char sekvenci s, použijte Rune.ToString metodu Rune.EncodeToUtf16 nebo .
Vzhledem k tomu, že libovolná skalární hodnota Unicode je reprezentovatelná jedním párem nebo náhradní dvojicí, může být libovolná instance reprezentována char Rune nejvíce 2 char instancemi. Pomocí Rune.Utf16SequenceLength můžete zobrazit, kolik char instancí je potřeba k reprezentaci Rune instance.
Další informace o typu .NET Rune najdete v referenčních informacích Rune k rozhraní API.
Clustery Grapheme
To, co vypadá jako jeden aktér, může být výsledkem kombinace více bodů kódu, takže popisnější termín, který se často používá místo char char "acter", je shluk grapheme. Ekvivalentní výraz v .NET je textový prvek.
Vezměte v úvahu instance string "a", "á", "á" a " 👩🏽🚒 ". Pokud je operační systém zpracovává podle standardu Unicode, zobrazí se každá z těchto instancí jako jeden textový string prvek nebo cluster grapheme. Poslední dva jsou však reprezentovány více než jedním bodem kódu skalární hodnoty.
string"a" je reprezentováno jednou skalární hodnotou a obsahuje jednu
charinstanci.U+0061 LATIN SMALL LETTER A
Hodnota string "á" je reprezentována jednou skalární hodnotou a obsahuje jednu
charinstanci.U+00E1 LATIN SMALL LETTER A WITH ACUTE
string"á" vypadá stejně jako "á", ale reprezentuje se dvěma skalárními hodnotami a obsahuje
chardvě instance.U+0061 LATIN SMALL LETTER AU+0301 COMBINING ACUTE ACCENT
Nakonec je string "
👩🏽🚒" reprezentováno čtyřmi skalárními hodnotami a obsahuje sedmcharinstancí.U+1F469 WOMAN(doplňkový rozsah vyžaduje náhradní dvojici)U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4(doplňkový rozsah vyžaduje náhradní dvojici)U+200D ZERO WIDTH JOINERU+1F692 FIRE ENGINE(doplňkový rozsah vyžaduje náhradní dvojici)
V některých předchozích příkladech , jako je například kombinující modifikátor diakritiky nebo modifikátor tónu snědí, se bod kódu na obrazovce nezobrazí jako samostatný prvek. Místo toho slouží k úpravě vzhledu textového prvku, který před něj přišel. Tyto příklady ukazují, že může trvat několik skalárních hodnot, než se to, co si myslíme jako jeden "acter" nebo char "grapheme cluster".
Pokud chcete vytvořit výčet clusterů grapheme objektu , použijte string třídu , jak je StringInfo znázorněno v následujícím příkladu. Pokud znáte Swift, je typ .NET koncepčně podobný typu StringInfo character Swiftu.
Příklad: count char , a text element Rune instances
V rozhraních .NET API se cluster grapheme nazývá textový prvek. Následující metoda ukazuje rozdíly mezi char instancemi Rune elementů , a text v string objektu :
Pokud tento kód spustíte v .NET Framework nebo .NET Core 3.1 nebo starší, zobrazí se počet textových prvků emoji 4 . Je to způsobené chybou ve StringInfo třídě , která je opravena v rozhraní .NET 5.
Příklad: Rozdělení string instancí
Při string rozdělování instancí se vyhněte rozdělení náhradních párů a clusterů grapheme. Představte si následující příklad nesprávného kódu, který má vkládat konce řádků každých 10 char akterů v string :
Vzhledem k tomu, že tento kód vytvoří výčet instancí, rozdělí se náhradní pár, který se stane vychýlením hranice 10, a mezi ně se vloženého char char nového řádku. Toto vložení zavádí poškození dat, protože body náhradního kódu mají smysl pouze jako páry.
Riziko poškození dat není eliminováno, pokud místo instancí provedete výčet instancí Rune char (skalárních hodnot). Sada instancí Rune může vytvořit cluster grapheme, který odchýlí hranici 10. char Pokud je sada clusteru grapheme rozdělená, nelze ji správně interpretovat.
Lepším přístupem je přerušení string počítáním clusterů grapheme nebo textových prvků, jako v následujícím příkladu:
Jak už jsme ale zmínili dříve, v implementacích rozhraní .NET jiných než .NET 5 může třída nesprávně zpracovat některé StringInfo clustery grapheme.
UTF-8 a UTF-32
Předchozí části se zaměřily na UTF-16, protože to .NET používá ke kódování string instancí. Existují i jiné systémy kódování pro Unicode – UTF-8 a UTF-32. Tato kódování používají 8bitové jednotky kódu a 32bitové jednotky kódu v uvedeném pořadí.
Stejně jako UTF-16 vyžaduje UTF-8 několik jednotek kódu, které představují některé skalární hodnoty Unicode. UTF-32 může představovat libovolnou skalární hodnotu v jedné 32bitové jednotce kódu.
Tady je několik příkladů, které ukazují, jak je stejný bod kódu Unicode reprezentován v každém z těchto tří systémů kódování 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 jsme uvedli dříve, jedna jednotka kódu UTF-16 z náhradní dvojice nemá smysl. Stejným způsobem nemá jedna jednotka kódu UTF-8 smysl, pokud je v sekvenci dvou, tří nebo čtyř použitých k výpočtu skalární hodnoty.
Endianness
V rozhraní .NET jsou jednotky kódu UTF-16 uloženy v souvislé paměti jako posloupnost 16bitových celých čísel string ( char instancí). Bity jednotlivých jednotek kódu jsou rozloženy podle připravenosti aktuální architektury.
Na malé endovské architektuře by se skládající se z bodů kódu UTF-16 byly v paměti rozloženy string [ D801 DCCC ] jako bajty [ 0x01, 0xD8, 0xCC, 0xDC ] . V architektuře big-endian by to samé bylo rozloženo v string paměti jako bajty [ 0xD8, 0x01, 0xDC, 0xCC ] .
Počítačové systémy, které spolu vzájemně komunikují, se musí shodnout na reprezentaci dat, která přechádují kabel. Většina síťových protokolů používá UTF-8 jako standard při přenosu textu, částečně proto, aby nedocházelo k problémům, které by mohly být důsledkem toho, že velký počítač komunikuje s malým endianem. Skládající se z bodů kódu UTF-8 budou vždy reprezentovány jako string [ F0 90 93 8C ] bajty [ 0xF0, 0x90, 0x93, 0x8C ] bez ohledu na endianness.
Pokud aplikace .NET k přenosu textu používají UTF-8, často používají kód jako v následujícím příkladu:
string stringToWrite = GetString();
byte[] stringAsUtf8Bytes = Encoding.UTF8.GetBytes(stringToWrite);
await outputStream.WriteAsync(stringAsUtf8Bytes, 0, stringAsUtf8Bytes.Length);
V předchozím příkladu metoda Encoding.UTF8.GetBytes dekóduje UTF-16 zpět do řady skalárních hodnot Unicode, pak tyto skalární hodnoty znovu zakóduje do UTF-8 a výslednou sekvenci umístí string do byte pole. Metoda Encoding.UTF8.GetString provádí opačnou transformaci a převádí pole UTF-8 byte na UTF-16. string
Upozornění
Vzhledem k tomu, že utf-8 je na internetu běžné, může být lákavé číst nezpracované bajty z přenosu a považovat data za UTF-8. Měli byste však ověřit, že je skutečně ve správném formátu. Škodlivý klient může vaší službě odeslat kód UTF-8 ve špatném formátu. Pokud s daty pracujete, jako by byla správně formátovaná, mohlo by dojít k chybám nebo bezpečnostním problémům ve vaší aplikaci. K ověření dat UTF-8 můžete použít metodu, jako je , která provede ověření při převodu Encoding.UTF8.GetString příchozích dat na string .
Kódování ve správném formátu
Kódování Unicode ve správném formátu je jednotka kódu, kterou lze jednoznačně dekódovat a bez chyb dekódovat do sekvence string skalárních hodnot Unicode. Data ve správném formátu lze volně překódovat mezi UTF-8, UTF-16 a UTF-32.
Otázka, jestli je sekvence kódování ve správném formátu nebo ne, nesouvisí s endiánem architektury počítače. Na počítačích big-endian i little-endian je sekvence UTF-8 ve špatném formátu stejně.
Tady je několik příkladů kódování ve špatném formátu:
V UTF-8 je sekvence
[ 6C C2 61 ]špatně formátovaná, protože ji nelzeC261sledovat.V UTF-16 je sekvence (nebo v
[ DC00 DD00 ]jazyce C#) špatně formátovaná, protože po nízkém náhradním znaku nemůže následovat další nízká string"\udc00\udd00"náhradníDC00hodnotaDD00.V UTF-32 je sekvence špatně formátovaná, protože je mimo
[ 0011ABCD ]0011ABCDrozsah skalárních hodnot Unicode.
V .NET string instance téměř vždy obsahují data UTF-16 ve správném formátu, ale to není zaručeno. Následující příklady ukazují platný kód jazyka C#, který v instancích vytváří chybně formátovaná data UTF-16. string
Literál ve špatném formátu:
const string s = "\ud800";Dílčí string hodnota, která rozdělí náhradní dvojici:
string x = "\ud83e\udd70"; // "🥰" string y = x.Substring(1, 1); // "\udd70" standalone low surrogate
Rozhraní API jako nikdy Encoding.UTF8.GetString nevrací instance ve string špatném formátu. Encoding.GetString Metody a detekují ve vstupu sekvence ve špatném formátu Encoding.GetBytes a provádějí char substituci acterů při generování výstupu. Pokud například ve vstupu (mimo rozsah Encoding.ASCII.GetString(byte[]) U+0000..U+007F) uvidí i další byte mimo ASCII, vloží do vrácené instance znak string ?. Encoding.UTF8.GetString(byte[]) nahradí sekvence UTF-8 ve vrácené instanci za U+FFFD REPLACEMENT CHARACTER ('�') string . Další informace najdete v částech Standard Unicode, Oddíly 5.22 a 3.9.
Předdefinované třídy lze také nakonfigurovat tak, aby místo provádění substituce aktérů vytvářely výjimku, pokud jsou vidět nesprávně Encoding char formátované sekvence. Tento přístup se často používá v aplikacích citlivých na zabezpečení, kde char Acter nahrazení nemusí být přijatelné.
byte[] utf8Bytes = ReadFromNetwork();
UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
string asString = encoding.GetString(utf8Bytes); // will throw if 'utf8Bytes' is ill-formed
Informace o použití vestavěných Encoding tříd naleznete v tématu How to use char Acter Encoding Classes in .NET.