Karakterkódolás a .NET-ben

Ez a cikk bemutatja a .NET által használt karakterkódoló rendszereket. A cikk bemutatja, hogyan működnek a UnicodeStringChar, az UTF-16 és Rune az StringInfoUTF-8 típusok.

A karakter kifejezést itt annak általános értelmében használják, hogy az olvasó mit érzékel egyetlen megjelenítési elemként. Gyakori példák az "a" betű, a "@" szimbólum és az emoji "🐂". Néha az egy karakternek tűnő karakter valójában több független megjelenítési elemből áll, ahogy azt a grafémafürtök szakasza is ismerteti.

Az string és a char típusok

Az string osztály egy példánya szöveget képvisel. Az A string logikailag 16 bites értékek sorozata, amelyek mindegyike a char szerkezet egy-egy példánya. A string.Length tulajdonság a string példány char példányainak számát adja vissza.

Az alábbi mintafüggvény kiírja a char-beli összes string értéket hexadecimális jelöléssel.

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

Adja át a string "Hello" értéket ennek a függvénynek, és a következő kimenetet kapja:

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')

Minden karaktert egyetlen char érték jelöl. Ez a minta a világ legtöbb nyelvére igaz. Például itt van két kínai karakter jelentése, amelyek úgy hangzanak, mint nǐ hǎo és azt jelentik, hogy hello:

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

Egyes nyelveknél, valamint egyes szimbólumok és hangulatjelek esetében azonban két char példány szükséges egyetlen karakter megjelenítéséhez. Például hasonlítsa össze a karaktereket és char példányokat abban a szóban, ami az Osage jelentése az Osage nyelvben.

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')

Az előző példában a szóköz kivételével minden karaktert két char példány jelöl.

Egyetlen Unicode emojit két chars is jelöl, ahogy az alábbi példában látható egy ökör emoji:

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

Ezek a példák azt mutatják, hogy a példányok számát string.Length jelző érték charnem feltétlenül jelzi a megjelenített karakterek számát. Egyetlen char példány önmagában nem feltétlenül jelöl karaktert.

Azokat char a párokat, amelyek egyetlen karakterre vannak képezve, helyettesítő pároknak nevezzük. A működésük megértéséhez ismernie kell a Unicode és az UTF-16 kódolást.

Unicode-kódpontok

A Unicode egy nemzetközi kódolási szabvány, amely különböző platformokon és különböző nyelveken és szkriptekben használható.

A Unicode Standard több mint 1,1 millió kódpontot határoz meg. A kódpont egy egész szám, amely 0 U+10FFFF és (decimális 1 114 1111) között lehet. Egyes kódpontok betűkhez, szimbólumokhoz vagy emojikhoz vannak rendelve. Mások olyan műveletekhez vannak rendelve, amelyek szabályozzák a szöveg vagy a karakterek megjelenítését, például egy új sorra való ugrást. Sok kódpont még nincs hozzárendelve.

Íme néhány példa a kódpont-hozzárendelésekre, amelyekben Unicode-diagramokra mutató hivatkozások találhatók:

Decimális Hexadecimális Példa Leírás
10 U+000A n/a VONALCSATORNA
97 U+0061 egy LATIN KISBETŰ A
562 U+0232 Ȳ LATIN NAGYBETŰ Y MACRONNAL
68,675 U+10C43 𐱃 RÉGI TÜRK BETŰ ORKHON AT
127,801 U+1F339 🌹 ROSE emoji

A kódpontokra szokás hivatkozni a szintaxis U+xxxxhasználatával, ahol xxxx a hexán kódolt egész szám értéke van.

A kódpontok teljes tartományán belül két altartomány található:

  • Az alapszintű többnyelvű sík (BMP) a tartományban U+0000..U+FFFF. Ez a 16 bites tartomány 65 536 kódpontot biztosít, ami elegendő ahhoz, hogy lefedje a világ írási rendszereinek többségét.
  • A kiegészítő kódpontok a tartományban U+10000..U+10FFFF. Ez a 21 bites tartomány több mint egymillió további kódpontot biztosít, amelyek kevésbé ismert nyelvekhez és más célokra, például emojikhoz használhatók.

Az alábbi ábra a BMP és a kiegészítő kódpontok közötti kapcsolatot mutatja be.

BMP és kiegészítő kódpontok

UTF-16 kódegységek

A 16 bites Unicode-átalakítási formátum (UTF-16) egy karakterkódoló rendszer, amely 16 bites kódegységeket használ a Unicode-kódpontok megjelenítéséhez. A .NET az UTF-16 használatával kódolja a szövegeket egy karakterláncban string. A char példányok egy 16 bites kódegységet jelölnek.

Egyetlen 16 bites kódegység az alapszintű többnyelvű sík 16 bites tartományának bármely kódpontját képviselheti. A kiegészítő tartomány kódpontjaihoz azonban két char példányra van szükség.

Helyettesítő párok

A két 16 bites érték egyetlen 21 bites értékre való fordítását egy speciális tartomány, az úgynevezett helyettesítő kódpontok segítik elő, U+D800U+DFFF amelyek között a (decimális 55 296 és 57 343 közötti) értékek szerepelnek.

Az alábbi ábra a BMP és a helyettesítő kódpontok közötti kapcsolatot mutatja be.

BMP és helyettesítő kódpontok

Ha egy magas helyettesítő kódpontot (U+D800..U+DBFF) azonnal követ egy alacsony helyettesítő kódpont (U+DC00..U+DFFF), a párt kiegészítő kódpontként értelmezzük az alábbi képlettel.

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

Ugyanez a képlet decimális jelöléssel:

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

A magas helyettesítő kódpontnak nincs nagyobb számértéke, mint egy alacsony helyettesítő kódpontnak. A magas helyettesítő kódpont neve "magas", mivel a 20 bites kódponttartomány magasabbrendű 10 bitjének kiszámítására szolgál. Az alacsony helyettesítő kódpont az alacsonyabb rendű 10 bit kiszámítására szolgál.

Például a helyettesítő párnak 0xD83C megfelelő tényleges kódpont, amely 0xDF39 a következőképpen van kiszámítva:

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

Ugyanez a számítás decimális jelöléssel:

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

Az előző példa azt mutatja be, hogy "\ud83c\udf39" a korábban említett kódpont UTF-16 kódolása U+1F339 ROSE ('🌹') .

Unicode skaláris értékek

A Unicode skaláris érték kifejezés a helyettesítő kódpontoktól eltérő összes kódpontra vonatkozik. Más szóval a skaláris érték bármely olyan kódpont, amely egy karakterhez van hozzárendelve, vagy a jövőben hozzárendelhető egy karakterhez. A "Karakter" itt mindenre utal, amely hozzárendelhető egy kódponthoz, beleértve például a szöveg vagy a karakterek megjelenítését vezérlő műveleteket.

Az alábbi ábra a skaláris érték kódpontjait szemlélteti.

Skaláris értékek

A Rune típus skaláris értékként

Fontos

A Rune típus nem érhető el a .NET-keretrendszerben.

A .NET-ben a System.Text.Rune típus Unicode skaláris értéket jelöl.

A Rune konstruktorok ellenőrzik, hogy az eredményül kapott példány érvényes Unicode skaláris érték-e, ellenkező esetben kivételt okoznak. Az alábbi példa olyan kódot mutat be, amely sikeresen példányosítja a Rune példányokat, mivel a bemenet érvényes skaláris értékeket tartalmaz.

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');

Az alábbi példa kivételt jelez, mert a kódpont a helyettesítő tartományban van, és nem része helyettesítő párnak:

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

Az alábbi példa kivételt jelez, mert a kódpont túllépi a kiegészítő tartományt:

Rune g = new Rune(0x12345678);

Rune használati példa: betűk kis- és nagybetűssé alakítása

Egy olyan API, amely egy char skaláris értéket tartalmazó kódponttal dolgozik, nem működik megfelelően, ha az char helyettesítő párból származik. Vegyük például a következő metódust, amely minden egyes char-t meghívja a string-ben:

// 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();
}

Ha a inputstring kisbetűs Deseret betűt er (𐑉) tartalmazza, ez a kód nem konvertálja nagybetűvé 𐐡. A kód minden helyettesítő kódponton külön hív char.ToUpperInvariant , U+D801 és U+DC49. De U+D801 önmagában nem rendelkezik elegendő információval ahhoz, hogy kisbetűként azonosítsa, ezért char.ToUpperInvariant hagyja békén. És ugyanúgy kezeli U+DC49 . Az eredmény az, hogy a inputstring "𐑉" kisbetű nem lesz "𐑉" nagybetűssé konvertálva.

Íme két lehetőség a string helyes nagybetűs átalakítására:

  • Hívja meg a String.ToUpperInvariant, a string bemeneten ahelyett, hogy char-by-char iterálna. A string.ToUpperInvariant metódus hozzáféréssel rendelkezik az egyes helyettesítő párok mindkét részéhez, így minden Unicode-kódpontot megfelelően kezelhet.

  • A Unicode skaláris értékeit Rune példányokként iteráljuk, nem pedig char példányokként, ahogy az alábbi példában látható. Mivel a Rune példány érvényes Unicode skaláris érték, átadható az API-knak, amelyek várhatóan skaláris értéken fognak működni. A következő példában látható hívás Rune.ToUpperInvariant például helyes eredményeket ad:

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

Egyéb Rune API-k

A Rune típus számos char API analógjait teszi elérhetővé. A következő metódusok például statikus API-kat tükröznek a char típuson:

A Rune példányból a nyers skaláris érték lekéréséhez használja a Rune.Value tulajdonságot.

A Rune példányt vissza lehet konvertálni char sorozattá a Rune.ToString vagy Rune.EncodeToUtf16 módszer használatával.

Mivel bármely Unicode skaláris érték egyetlen char vagy helyettesítő pár által ábrázolható, a Rune példányokat legfeljebb 2 char példány jelölheti. A Rune.Utf16SequenceLength használatával megnézheti, hogy hány char példányra van szükség egy Rune példány reprezentálásához.

A .NET Rune típusával kapcsolatos további információkért tekintse meg az Rune API-hivatkozást.

Grapheme-fürtök

Egy karakter akár több kódpont kombinációjából is állhat, ezért a "karakter" helyett gyakran használt leíróbb kifejezés a graféma-csoport. A szövegelem a .NET megfelelő kifejezése.

Vegye figyelembe az "a string ", "á", "á" és "👩🏽‍🚒" példányokat. Ha az operációs rendszer a Unicode szabvány által meghatározott módon kezeli őket, mindegyik string példány egyetlen szöveges elemként vagy gráffürtként jelenik meg. Az utolsó kettőt azonban egynél több skaláris értékkódpont jelöli.

  • Az string "a"-t egy skaláris érték jelöli, és egy példányt char tartalmaz.

    • U+0061 LATIN SMALL LETTER A
  • Az string "á"-t egy skaláris értékkel jelölik, és tartalmaz egy példányt char.

    • U+00E1 LATIN SMALL LETTER A WITH ACUTE
  • Az string "á" ugyanúgy néz ki, mint az "á", de két skaláris érték jelöli, és két char példányt tartalmaz.

    • U+0061 LATIN SMALL LETTER A
    • U+0301 COMBINING ACUTE ACCENT
  • Végül a string "👩🏽‍🚒" értékét négy skaláris érték jelöli, és hét char példányt tartalmaz.

    • U+1F469 WOMAN (kiegészítő tartomány, pótpárt igényel)
    • U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4 (kiegészítő tartomány, pótpárt igényel)
    • U+200D ZERO WIDTH JOINER
    • U+1F692 FIRE ENGINE (kiegészítő tartomány, pótpárt igényel)

Néhány fenti példában - például a kombináló ékezetmódosító vagy a bőrtónus módosító - a kódpont nem jelenik meg önálló elemként a képernyőn. Ehelyett egy előtte lévő szövegelem megjelenését módosítja. Ezek a példák azt mutatják, hogy több skaláris értékre is szükség lehet ahhoz, hogy egyetlen "karakterként" vagy "grapheme-fürtként" definiáljuk azt, amit gondolunk.

Egy string gráfém csoportok felsorolásához használja a StringInfo osztályt, ahogyan az alábbi példában látható. Ha ismeri a Swiftet, a .NET StringInfo típus fogalmilag hasonló a Swift típusáhozcharacter.

Példa: darabszám char, Runeés szövegelem-példányok

A .NET API-kban a gráffürtöt szöveges elemnek nevezzük. Az alábbi módszer bemutatja a char, Rune és szövegelem példányok közötti különbségeket egy string-ben.

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

Példa: string példányok felosztása

Példányok felosztásakor string kerülje el a helyettesítő párok felosztását és a gráféma klaszterek felosztását. Tekintse meg a következő példát a helytelen kódra, amely 10 karakterenként szeretne sortöréseket beszúrni a stringkövetkezőbe:

// 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();
}

Mivel ez a kód számba char veszi a példányokat, egy helyettesítő pár, amely történetesen egy 10-határtchar keresztez, fel lesz osztva, és egy új vonal lesz beszúrva közöttük. Ez a beszúrás adatsérülést vezet be, mivel a helyettesítő kódpontok csak párként értelmezhetők.

Az adatsérülés lehetősége nem szűnik meg, ha Rune példányokat (skaláris értékeket) sorol fel a char példányok helyett. A Rune példányok halmaza olyan grafémacsoportot alkothat, amely átfed egy 10-char határt. Ha a gráffürt-készlet fel van osztva, az nem értelmezhető megfelelően.

A jobb módszer a string gráffürtök vagy szöveges elemek számlálásával történő megtörése, ahogyan az alábbi példában is látható:

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)
        {
            builder.AppendLine(); // newline
        }
        textElementCount++;
    }

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

}

Ahogy korábban említettük, a .NET 5 előtt az StringInfo osztályban hiba történt, ami miatt néhány gráffürt helytelenül lett kezelve.

UTF-8 és UTF-32

Az előző szakaszok az UTF-16 formátumra koncentráltak, mert a .NET ezt használja a string példányok kódolásához. Vannak más kódolási rendszerek a Unicode - UTF-8 és UTF-32. Ezek a kódolások 8 bites kódegységeket és 32 bites kódegységeket használnak.

Az UTF-16-hoz hasonlóan az UTF-8-hoz is több kódegységre van szükség bizonyos Unicode skaláris értékek megjelenítéséhez. Az UTF-32 bármilyen skaláris értéket képviselhet egyetlen 32 bites kódegységben.

Íme néhány példa arra, hogyan jelenik meg ugyanaz a Unicode-kódpont a három Unicode kódolási rendszer mindegyikében:

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)

Ahogy korábban említettük, egyetlen UTF-16 kódegység egy helyettesítő párból önmagában értelmetlen. Ugyanígy egyetlen UTF-8 kódegység önmagában is értelmetlen, ha egy skaláris érték kiszámításához két, három vagy négy sorozatban van.

Megjegyzés

A C# 11-től kezdve az UTF-8 string literálokat az "u8" utótaggal jelölheti egy literálon string. Az UTF-8 string literálokkal kapcsolatos további információkért tekintse meg a C# útmutató beépített referenciatípusait string ismertető cikk "literálok" szakaszát.

Bájt sorrendösség

A .NET-ben az UTF-16 kódegységek string egybefüggő memóriában vannak tárolva, 16 bites egész számok (char példányok) sorozataként. Az egyes kódegységek bitjei az aktuális architektúra endianitásának megfelelően vannak meghatározva.

A little-endian architektúrán az string UTF-16 kódpontokból álló [ D801 DCCC ] a memóriában bájtokként [ 0x01, 0xD8, 0xCC, 0xDC ] kerülne elrendezésre. Egy big-endian architektúrán a string ugyanúgy kerülne elhelyezésre a memóriában, mint a bájtok [ 0xD8, 0x01, 0xDC, 0xCC ].

Az egymással kommunikáló számítógépes rendszereknek meg kell egyeznie a vezetéken áthaladó adatok ábrázolásában. A legtöbb hálózati protokoll az UTF-8 szabványt használja a szövegek továbbításához, részben azért, hogy elkerülje azokat a problémákat, amelyek egy kis endian géppel kommunikáló big endian gépekből eredhetnek. Az string UTF-8 kódpontokból álló kódpontok [ F0 90 93 8C ] mindig bájtként [ 0xF0, 0x90, 0x93, 0x8C ] jelennek meg, függetlenül az endianitástól.

Ha az UTF-8-at szeretné használni a szöveg továbbításához, a .NET-alkalmazások gyakran az alábbi példához hasonló kódot használnak:

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

Az előző példában az Encoding.UTF8.GetBytes metódus visszaalakítja a UTF-16 kódolást Unicode skaláris értékek sorozatává, majd ezeket a skaláris értékeket újra kódolja UTF-8 formátumba, és az így kapott sorozatot egy tömbbe helyezi. A Encoding.UTF8.GetString metódus az ellenkező átalakítást hajtja végre, és UTF-8 byte tömböt konvertál UTF-16-rastring.

Figyelmeztetés

Mivel az UTF-8 köznapi az interneten, csábító lehet nyers bájtokat olvasni a vezetékből, és úgy kezelni az adatokat, mintha UTF-8 lenne. Azonban ellenőrizze, hogy valóban megfelelően van-e kialakítva. Előfordulhat, hogy egy rosszindulatú ügyfél rosszul formázott UTF-8-at küld a szolgáltatásnak. Ha úgy használja az adatokat, mintha azok megfelelően lettek volna formázva, az hibákat vagy biztonsági réseket okozhat az alkalmazásban. Az UTF-8-adatok érvényesítéséhez használhat egy olyan metódust mint Encoding.UTF8.GetString, amely érvényesítést hajt végre, miközben a bejövő adatokat átalakítja egy string-re.

Jól formázott kódolás

A jól formázott Unicode-kódolás olyan kódegységsorozat, amely egyértelműen és hiba nélkül dekódolható Unicode skaláris értékek sorozatává. A jól formázott adatok szabadon átkódolhatók az UTF-8, az UTF-16 és az UTF-32 között.

Az a kérdés, hogy egy kódolási sorozat jól formázott-e vagy sem, nem függ a gép architektúrájának végiségétől. Egy rosszul formázott UTF-8 sorozat mind a big-endian, mind a little-endian gépeken ugyanúgy rosszul van formázva.

Íme néhány példa a nem formázott kódolásokra:

  • Az UTF-8-ban a sorozat [ 6C C2 61 ] helytelen, mert C2-t nem követheti 61.

  • Az UTF-16-ban a [ DC00 DD00 ] szekvencia (vagy C#-ban az string"\udc00\udd00" szekvencia) rossz formátumú, mert az alacsony helyettesítő karakter DC00 nem követhető egy másik alacsony helyettesítő karakterrel DD00.

  • Az UTF-32-ben a sorozat [ 0011ABCD ] helytelenül megformált, mert 0011ABCD kívül esik a Unicode skaláris értékeinek tartományán.

A .NET-ben a string példányok szinte mindig jól formázott UTF-16 adatokat tartalmaznak, de ez nem garantált. Az alábbi példák érvényes C# kódot mutatnak be, amely hibásan formázott UTF-16 adatokat hoz létre a string példányokban.

  • Egy rosszul formázott literál:

    const string s = "\ud800";
    
  • Egy alsztring, amely feloszt egy helyettesítési párt.

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

Az olyan API-k, mint a Encoding.UTF8.GetString, soha nem adnak vissza hibásan formázott string példányokat. Encoding.GetString és Encoding.GetBytes a metódusok a bemenetben rosszul formázott sorozatokat észlelnek, és a kimenet létrehozásakor karakterhelyettesítést hajtanak végre. Ha például Encoding.ASCII.GetString(byte[]) nem ASCII bájt jelenik meg a bemenetben (az U+0000..U+007F tartományon kívül), a visszaadott string példányba egy "?" értéket szúr be. Encoding.UTF8.GetString(byte[]) a hibásan formázott UTF-8 sorozatokat a visszaadott string példányban U+FFFD REPLACEMENT CHARACTER ('�')-ra cseréli. További információ : Unicode Standard, 5.22 és 3.9.

A beépített Encoding osztályok úgy is konfigurálhatók, hogy kivételt okozzanak ahelyett, hogy karakterhelyettesítést hajtanak végre, ha rosszul formázott sorozatok jelennek meg. Ezt a megközelítést gyakran használják olyan biztonsági szempontból érzékeny alkalmazásokban, ahol a karakterhelyettesítés nem feltétlenül elfogadható.

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

A beépített osztályok használatáról további információt a .NET karakterkódoló Encoding osztályainak használata című témakörben talál.

Lásd még