Kódování znaků v rozhraní .NET

Tento článek obsahuje úvod do systémů char kódování Acter, které používá .NET. Tento článek vysvětluje, jak typy , , a String Char fungují s Rune StringInfo unicode, UTF-16 a UTF-8.

Pojem char acter se zde používá v obecném smyslu toho, co čtenář vnímá jako jeden prvek zobrazení. Mezi běžné příklady patří písmeno "a", symbol "@" a emoji 🐂 " ". Někdy to, co vypadá jako jeden aktér, se ve skutečnosti skládá z několika nezávislých prvků zobrazení, jak vysvětluje část o char clusterech Grapheme.

Typy string char a

Instance třídy string představuje nějaký text. Je logicky posloupnost 16bitových hodnot, z nichž každá je string instancí char struktury. . string Vlastnost Length vrátí počet char instancí v string instanci.

Následující ukázková funkce vytiskne hodnoty v šestnáctkové notaci všech char instancí v objektu string :

Do této string funkce předejte "Hello" 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')

Každý char aktér je reprezentován jednou char hodnotou. Tento vzor platí pro většinu světových jazyků. Tady je například výstup pro dva čínský aktéry, kteří znějí char jako n

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

U některých jazyků a u některých symbolů a emoji je však potřeba, aby dva char výskyty představovaly jednoho char aktéra. Porovnejte například aktéry a instance ve slově , což char char znamená Osage v jazyce 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')

V předchozím příkladu je každý char acter s výjimkou prostoru reprezentován dvěma char instancemi.

Jedna emoji v kódování Unicode je také reprezentována dvěma y, jak je vidět v následujícím příkladu znázorňující char emoji typu ox:

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

Tyto příklady ukazují, že hodnota , která označuje počet instancí, nemusí nutně indikovat počet string.Length char zobrazených char aktéři. Jedna char instance sama o sobě nemusí nutně představovat char aktéra.

Páry, char které se mapuje na jednoho char aktéra, se nazývají náhradní páry. Abyste pochopili, jak fungují, musíte porozumět 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 s různými jazyky a skripty.

Standard Unicode definuje více než 1,1 milionu bodů kódu. Bod kódu je celočíselná hodnota, která může být v rozsahu od 0 U+10FFFF do (desítkové číslo 1 114 111). Některé body kódu jsou přiřazeny písmenům, symbolům nebo emoji. Jiné jsou přiřazeny k akcím, které řídí, jak se zobrazuje text nebo aktéři, například k postupu char na nový řádek. Mnoho bodů kódu ještě není přiřazeno.

Tady je několik příkladů přiřazení bodů kódu s odkazy na ts sady Unicode, ve kterých char se zobrazují:

Decimal Hex Příklad Description
10 U+000A ODSOU ČÁRA
97 U+0061 pro MALÉ PÍSMENO LATINKY A
562 U+0232 Ȳ VELKÉ PÍSMENO LATINKY Y S MAKNEM
68,675 U+10C43 𐱃 OLD UŽÍKANÉ PÍSMENO ORŠTINA V
127,801 U+1F339 🌹 EMOJI – EMOJI

Na body kódu se obvykle odkazuje pomocí syntaxe , kde je celočíselná hodnota zakódovaná v U+xxxx xxxx šestnáctku.

V rámci plného rozsahu bodů kódu existují dva podrozsahy:

  • Základní vícejazyčná rovina (BMP) v rozsahu U+0000..U+FFFF . Tento 16bitový rozsah poskytuje 65 536 bodů kódu, které jsou dostatečné pro pokrytí většiny systémů pro psaní na světě.
  • Doplňkové body kódu v rozsahu U+10000..U+10FFFF . Tento 21bitový rozsah poskytuje více než milion dalších bodů kódu, které lze použít pro méně známé jazyky a další účely, jako jsou emoji.

Následující diagram znázorňuje vztah mezi BMP a doplňkovými body kódu.

BMP a doplňkové body kódu

Jednotky kódu UTF-16

16bitový formát transformace Unicode(UTF-16)je systém kódování Acter, který k reprezentaci bodů kódu Unicode používá char 16bitové jednotky kódu. .NET používá UTF-16 ke kódování textu v string . Instance char představuje 16bitovou jednotku 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 jsou potřeba char dvě instance.

Náhradní páry

Překlad dvou 16bitových hodnot na jednu 21bitovou hodnotu je usnadněn speciálním rozsahem nazývaným body náhradního kódu od do U+D800 (desítkové číslo U+DFFF 55 296 až 57 343), včetně.

Následující diagram znázorňuje vztah mezi BMP a náhradními body kódu.

BMP a náhradní body kódu

Když je za bodem vysokého náhradního kódu ( ) okamžitě následován nízkým náhradním bodem kódu ( ), je pár interpretován jako bod doplňkového kódu pomocí U+D800..U+DBFF U+DC00..U+DFFF následujícího vzorce:

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

Tady je stejný vzorec, který používá desítkový zápis:

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

Vysoký bod náhradního kódu nemá vyšší číselnou hodnotu než nízký bod náhradního kódu. Vysoký bod náhradního kódu se nazývá "high", protože se používá k výpočtu 10 bitů 20bitového rozsahu bodů kódu vyššího řádu. Nízký bod náhradního kódu se používá k výpočtu 10 bitů nižšího řádu.

Například skutečný bod kódu, který odpovídá náhradní dvojici a je 0xD83C 0xDF39 vypočítán takto:

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

Tady je stejný výpočet s použitím 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 je "\ud83c\udf39" kódování UTF-16 dříve U+1F339 ROSE ('🌹') zmíněného bodu kódu.

Skalární hodnoty Unicode

Termín skalární hodnota Unicode odkazuje na všechny body kódu kromě bodů náhradního kódu. Jinými slovy, skalární hodnota je jakýkoli bod kódu, který je přiřazen aktéru nebo může být v char char budoucnu přiřazen aktérem. "Znak" zde odkazuje na cokoli, co lze přiřadit k bodu kódu, což zahrnuje například akce, které řídí, jak se text nebo char aktéři zobrazují.

Následující diagram znázorňuje body kódu skalární hodnoty.

Skalární hodnoty

Typ Rune jako skalární hodnota

Počínaje .NET Core 3.0 System.Text.Rune představuje typ skalární hodnotu Unicode. Runenení k dispozici v .NET Core 2.x nebo .NET Framework 4.x.

Konstruktory ověřují, zda je výsledná instance platnou skalární hodnotou Rune unicode, jinak vyvolá výjimku. Následující příklad ukazuje kód, který úspěšně vytvoří Rune instanci 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 náhradním rozsahu a není součástí náhradní dvojice:

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 velká a malá písmena

Rozhraní API, které přebírá a předpokládá, že pracuje s bodem kódu, který je skalární hodnotou, nefunguje správně, pokud je z char char náhradní dvojice. Představte si například následující metodu, která Char.ToUpperInvariant pro každý z nich volá char metodu v string :

Pokud input string obsahuje malé písmeno deseretu ( ), tento kód ho nepřevádí er na velká písmena ( 𐑉 𐐡 ). Kód volá char.ToUpperInvariant samostatně na každý náhradní bod kódu U+D801 a U+DC49 . Nemá ale U+D801 dostatek informací, aby ho identifikoval jako malé písmeno, takže char.ToUpperInvariant ho ponechá samostatně. A zpracovává U+DC49 se stejným způsobem. Výsledkem je, že malé písmeno ' 𐑉 ' v ' input string nebude převedeno na velká písmena ' 𐑉 '.

Tady jsou dvě možnosti pro správné převádění string na velká písmena:

  • Zavolejte String.ToUpperInvariant na vstup string místo iterace char -by- char . string.ToUpperInvariantMetoda má přístup k oběma částem každého náhradního páru, takže může správně zpracovat všechny body kódu Unicode.

  • Iterujte pomocí skalárních hodnot Unicode jako Rune instancí namísto char instancí, jak je znázorněno v následujícím příkladu. Vzhledem k Rune tomu, že instance je platná skalární hodnota Unicode, může být předána rozhraním API, která očekávají, že budou pracovat na skalární hodnotě. 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

RuneTyp zveřejňuje analogové typy mnoha char rozhraní API. Například následující metody zrcadlí statická rozhraní API pro daný char Typ:

Chcete-li získat nezpracovanou skalární hodnotu z Rune instance, použijte Rune.Value vlastnost.

Chcete-li převést Rune instanci zpět na sekvenci char s, použijte Rune.ToString metodu nebo Rune.EncodeToUtf16 .

Vzhledem k tomu, že jakákoli skalární hodnota Unicode je reprezentována jedním char nebo náhradním párem, Rune může být libovolná instance reprezentovaná ve většině dvou char instancí. Použijte Rune.Utf16SequenceLength k zobrazení, kolik char instancí je potřeba k reprezentaci Rune instance.

Další informace o typu .NET najdete v referenčních informacích k Rune Rune rozhraní API.

Clustery Grapheme

Co vypadá jako jeden char Acter, může být výsledkem kombinace více bodů kódu, takže výstižnější pojem, který se často používá místo " char Acter" je cluster grapheme. Ekvivalentní termín v .NET je textový prvek.

Vezměte v úvahu string instance "a", "á", "á" a " 👩🏽‍🚒 ". Pokud je operační systém zpracuje podle standardu Unicode, každá z těchto string instancí se zobrazí jako jeden textový prvek nebo grapheme cluster. Ale poslední dva jsou reprezentovány více než jedním skalárním bodem kódu.

  • string"A" je reprezentován jednou skalární hodnotou a obsahuje jednu char instanci.

    • U+0061 LATIN SMALL LETTER A
  • string"Á" je reprezentován jednou skalární hodnotou a obsahuje jednu char instanci.

    • U+00E1 LATIN SMALL LETTER A WITH ACUTE
  • string"Á" vypadá stejně jako "á", ale je reprezentován dvěma skalárními hodnotami a obsahuje dvě char instance.

    • U+0061 LATIN SMALL LETTER A
    • U+0301 COMBINING ACUTE ACCENT
  • Nakonec string "" představují 👩🏽‍🚒 čtyři skalární hodnoty a obsahují sedm char instancí.

    • U+1F469 WOMAN (doplňkový rozsah, vyžaduje náhradní pár)
    • U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4 (doplňkový rozsah, vyžaduje náhradní pár)
    • U+200D ZERO WIDTH JOINER
    • U+1F692 FIRE ENGINE (doplňkový rozsah, vyžaduje náhradní pár)

V některých předchozích příkladech, například v kombinaci s modifikátorem akcent nebo v modifikátoru intonace skinu, se bod kódu na obrazovce nezobrazí jako samostatný prvek. Místo toho slouží k úpravě vzhledu textového prvku, který byl dodán před ním. Tyto příklady ukazují, že může trvat více skalárních hodnot, aby se zajistilo, co považujeme za jeden char cluster "Acter" nebo "grapheme".

Chcete-li vytvořit výčet clusterů grapheme v nástroji string , použijte StringInfo třídu, jak je znázorněno v následujícím příkladu. Pokud jste obeznámeni s SWIFT, typ .NET StringInfo je koncepčně podobný character typu SWIFT.

Příklad: počet char Rune instancí elementu text

V rozhraní .NET API se cluster grapheme nazývá textový prvek. Následující metoda ukazuje rozdíly mezi char Rune instancemi prvků textu, a v string :

pokud tento kód spustíte v .NET Framework nebo .net Core 3,1 nebo starším, zobrazí se počet prvků textu pro emoji 4 . To je způsobeno chybou ve StringInfo třídě, která je opravena v rozhraní .NET 5.

Příklad: rozdělení string instancí

Při rozdělování string instancí se nerozdělují náhradní páry a grapheme clustery. Vezměte v úvahu následující příklad nesprávného kódu, který je v úmyslu vkládat zalomení řádků každých 10 char acters v string :

Vzhledem k tomu, že tento kód vytváří výčet instancí, je náhradní pár, který je považován za překrytí char 10 char hranic, rozdělen a mezi nimi bude vložen nový řádek. Toto vložení zavádí poškození dat, protože náhradní body kódu jsou smysluplné jenom jako páry.

Možnost poškození dat není při vytváření výčtu Rune instancí (skalárních hodnot) namísto char instancí odstraněna. Sada Rune instancí může vytvořit cluster grapheme, který přechází na 10 char hranici. Pokud je sada clusterů grapheme rozdělená, nedá se správně interpretovat.

Lepším řešením je přerušit string počítání grapheme clusterů nebo textových prvků, jako v následujícím příkladu:

Jak bylo uvedeno dříve, ale v implementacích rozhraní .NET jiné než .NET 5, StringInfo může třída považovat některé clustery grapheme nesprávně.

UTF-8 a UTF-32

Předchozí části se zaměřují na UTF-16, protože to je to, co rozhraní .NET používá ke kódování string instancí. Pro Unicode- UTF-8 a UTF-32jsou k dispozici jiné systémy kódování. Tato kódování používají 8bitové jednotky kódu a 32 jednotek kódu v uvedeném pořadí.

Podobně jako UTF-16 vyžaduje UTF-8 několik jednotek kódu, které reprezentují některé skalární hodnoty Unicode. UTF-32 může představovat libovolnou skalární hodnotu v jedné 32-bitové kódové jednotce.

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 bylo uvedeno dříve, jedna jednotka kódu UTF-16 z náhradního páru nemá smysl sám o sobě. Stejně tak jedna jednotka kódu UTF-8 nemá význam, pokud je v sekvenci dvou, tří nebo čtyř používaných k výpočtu skalární hodnoty.

Endianitou

V rozhraní .NET jednotky kódu UTF-16 string jsou uloženy v souvislé paměti jako sekvence 16bitových celých čísel ( char instancí). Bity jednotlivých jednotek kódu jsou rozloženy podle kódování endian aktuální architektury.

V architektuře s malým počtem bitů by se string skládají z kódových bodů UTF-16 [ D801 DCCC ] v paměti jako bajty [ 0x01, 0xD8, 0xCC, 0xDC ] . V architektuře big-endian by se stejná velikost nahlásila string v paměti jako bajty [ 0xD8, 0x01, 0xDC, 0xCC ] .

Počítačové systémy, které vzájemně komunikují, musí souhlasit s tím, jak se data přenáší na síťový přenos. Většina síťových protokolů jako standard pro přenos textu používá UTF-8, aby nedocházelo k problémům, které by mohly nastat při komunikaci počítače big-endian s počítačem se systémem Little endian. stringBody kódu znakové sady UTF-8 [ F0 90 93 8C ] budou vždy reprezentovány jako bajty [ 0xF0, 0x90, 0x93, 0x8C ] bez ohledu na formát endian.

Chcete-li použít kódování UTF-8 pro přenos textu, aplikace .NET často používají kód podobný následujícímu 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 znak UTF-16 string zpět do série skalárních hodnot Unicode, poté tyto skalární hodnoty znovu ZAkóduje do znakové sady UTF-8 a umístí výslednou sekvenci 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 maloobchodech na internetu, může se stát, že se načtou nezpracované bajty z kabelu a data se budou považovat za, jako kdyby byla UTF-8. Měli byste ale ověřit, že je ve skutečnosti ve správném formátu. Uživatel se zlými úmysly může do vaší služby odeslat nesprávně formátovanou znakovou sadu UTF-8. Pokud s těmito daty pracujete, jako kdyby byla ve správném formátu, mohlo by dojít k chybám nebo bezpečnostním otvorům ve vaší aplikaci. Chcete-li ověřit data UTF-8, můžete použít metodu Encoding.UTF8.GetString , například, která provede ověření při převodu příchozích dat na string .

Kódování ve správném formátu

Dobře formátované kódování Unicode je string jednotky kódu, které lze dekódovat jednoznačně a bez chyby v sekvenci skalárních hodnot Unicode. Data ve správném formátu se dají překódovat volně a zpátky mezi UTF-8, UTF-16 a UTF-32.

Otázka, zda je sekvence kódování ve správném formátu nebo nesouvisí se Endian architektury počítače. Nesprávně vytvořená sekvence UTF-8 je nesprávně vytvořená stejným způsobem na počítačích se systémem big endian a little endian.

Tady je několik příkladů nesprávně vytvořených kódování:

  • V kódování UTF-8 je sekvence [ 6C C2 61 ] nesprávně vytvořená, protože C2 nemůže následovat 61 .

  • V kódování UTF-16 je sekvence [ DC00 DD00 ] (nebo v jazyce C#, a string "\udc00\udd00" ) nesprávně vytvořená, protože Nízká náhrada DC00 nemůže následovat za jinou nízkou náhradou DD00 .

  • V kódování UTF-32 je sekvence [ 0011ABCD ] nesprávně vytvořená, protože 0011ABCD je mimo rozsah skalárních hodnot Unicode.

V rozhraní .NET string instance téměř vždy obsahují dobře vytvořená data UTF-16, ale nejsou zaručena. Následující příklady znázorňují platný kód C#, který v instancích vytváří nesprávně se formátovanou data UTF-16 string .

  • Nesprávně vytvořený literál:

    const string s = "\ud800";
    
  • Sub string , která rozdělí náhradní pár:

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

Rozhraní API Encoding.UTF8.GetString , jako nikdy nevracejí nesprávně formátované string instance. Encoding.GetString a Encoding.GetBytes metody zjišťují ve vstupní sekvenci nesprávně formátované sekvence a char při generování výstupu provede substituci Acter. Například pokud se Encoding.ASCII.GetString(byte[]) ve vstupu zobrazí bajt jiného typu než ASCII (mimo rozsah U + 0000.. U + 007F), vloží do vrácené instance znak? string . Encoding.UTF8.GetString(byte[]) nahradí nesprávně vytvořené sekvence UTF-8 U+FFFD REPLACEMENT CHARACTER ('�') ve vrácené string instanci. Další informace najdete v části Standard Unicode, oddíly 5,22 a 3,9.

Vestavěné Encoding třídy lze také nakonfigurovat tak, aby vyvolaly výjimku místo toho, aby prováděly char Acter nahrazení, pokud jsou zobrazeny sekvence s chybnou strukturou. 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.

Viz také