Tekencodering in .NET

Dit artikel bevat een inleiding tot charcoderingssystemen die door .NET worden gebruikt. In het artikel wordt uitgelegd hoe de Stringtypen CharUnicode StringInfoRune, UTF-16 en UTF-8 werken.

De term character wordt hier gebruikt in de algemene zin van wat een lezer beschouwt als één weergave-element. Veelvoorkomende voorbeelden zijn de letter 'a', het symbool '@' en de emoji '🐂.' Soms bestaat één character uit meerdere onafhankelijke weergave-elementen, zoals in de sectie over grapheme-clusters wordt uitgelegd.

De string en char typen

Een exemplaar van de string klasse vertegenwoordigt tekst. Een string is logisch een reeks van 16-bits waarden, die elk een exemplaar van de char struct is. De string. De eigenschap Lengte retourneert het aantal char exemplaren in het string exemplaar.

Met de volgende voorbeeldfunctie worden de waarden in hexadecimale notatie van alle char exemplaren in een 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();
}

Geef de string 'Hallo' door aan deze functie en u krijgt de volgende uitvoer:

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

Elke character wordt vertegenwoordigd door één char waarde. Dat patroon geldt voor de meeste talen van de wereld. Hier ziet u bijvoorbeeld de uitvoer voor twee Chinese characters die klinken als nǐ hǎo en mean Hello:

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

Voor sommige talen en voor sommige symbolen en emoji's zijn er echter twee char exemplaren nodig om één character weer te geven. Vergelijk bijvoorbeeld de characters en char exemplaren in het woord dat Osage in de osage-taal betekent:

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

In het voorgaande voorbeeld wordt elke character behalve de ruimte vertegenwoordigd door twee char exemplaren.

Een enkele Unicode-emoji wordt ook vertegenwoordigd door twee chars, zoals te zien is in het volgende voorbeeld met een ox-emoji:

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

In deze voorbeelden ziet u dat de waarde van string.Length, waarmee het aantal char exemplaren wordt aangegeven, niet noodzakelijkerwijs het aantal weergegeven characteerfuncties aangeeft. char Eén exemplaar op zichzelf vertegenwoordigt niet noodzakelijkerwijs een character.

De char paren die aan één character worden toegewezen, worden surrogaatparen genoemd. Als u wilt weten hoe ze werken, moet u Unicode- en UTF-16-codering begrijpen.

Unicode-codepunten

Unicode is een internationale coderingsstandaard voor gebruik op verschillende platforms en met verschillende talen en scripts.

De Unicode-standaard definieert meer dan 1,1 miljoen codepunten. Een codepunt is een geheel getal dat kan variëren van 0 tot U+10FFFF (decimaal 1.114.111). Sommige codepunten worden toegewezen aan letters, symbolen of emoji. Anderen worden toegewezen aan acties die bepalen hoe tekst of characteerfuncties worden weergegeven, zoals naar een nieuwe regel gaan. Veel codepunten zijn nog niet toegewezen.

Hier volgen enkele voorbeelden van codepunttoewijzingen, met koppelingen naar Unicode-ts charwaarin ze worden weergegeven:

Decimal Hex Opmerking Beschrijving
10 U+000A N.v.t. REGELFEED
97 U+0061 a LATIJNSE KLEINE LETTER A
562 U+0232 Ȳ LATIJNSE HOOFDLETTER Y MET MACRON
68,675 U+10C43 𐱃 OUDE TURKIC LETTER ORKHON AT
127,801 U+1F339 🌹 ROSE emoji

Codepunten worden aangepast aangeduid met behulp van de syntaxis U+xxxx, waarbij xxxx de hex-gecodeerde gehele waarde is.

Binnen het volledige bereik van codepunten zijn er twee subbereiken:

  • De BMP (Basic Multilingual Plane) in het bereik U+0000..U+FFFF. Dit 16-bits bereik biedt 65.536 codepunten, genoeg om het merendeel van de schrijfsystemen van de wereld te dekken.
  • Aanvullende codepunten in het bereik U+10000..U+10FFFF. Dit 21-bits bereik biedt meer dan een miljoen extra codepunten die kunnen worden gebruikt voor minder bekende talen en andere doeleinden, zoals emoji's.

In het volgende diagram ziet u de relatie tussen het BMP en de aanvullende codepunten.

BMP- en aanvullende codepunten

UTF-16-code-eenheden

16-bits Unicode Transformation Format (UTF-16) is een charactercoderingssysteem dat 16-bits code-eenheden gebruikt om Unicode-codepunten weer te geven. .NET gebruikt UTF-16 om de tekst in een string. Een char exemplaar vertegenwoordigt een 16-bits code-eenheid.

Een enkele 16-bits codeeenheid kan elk codepunt in het 16-bits bereik van het meertalige basisvlak vertegenwoordigen. Maar voor een codepunt in het aanvullende bereik zijn twee char exemplaren nodig.

Surrogaatparen

De vertaling van twee 16-bits waarden naar één 21-bits waarde wordt mogelijk gemaakt door een speciaal bereik dat de surrogaatcodepunten wordt genoemd, van U+D800 tot U+DFFF (decimaal 55.296 tot 57.343), inclusief.

In het volgende diagram ziet u de relatie tussen de BMP en de surrogaatcodepunten.

BMP- en surrogaatcodepunten

Wanneer een hoog surrogaatcodepunt (U+D800..U+DBFF) onmiddellijk wordt gevolgd door een laag surrogaatcodepunt (U+DC00..U+DFFF), wordt het paar geïnterpreteerd als een aanvullend codepunt met behulp van de volgende formule:

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

Hier volgt dezelfde formule met decimale notatie:

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

Een hoog surrogaatcodepunt heeft geen hogere getalwaarde dan een laag surrogaatcodepunt. Het hoge surrogaatcodepunt wordt 'hoog' genoemd, omdat het wordt gebruikt voor het berekenen van de hogere volgorde van 10 bits van een 20-bits codepuntbereik. Het lage surrogaatcodepunt wordt gebruikt om de lagere volgorde van 10 bits te berekenen.

Het werkelijke codepunt dat overeenkomt met het surrogaatpaar 0xD83C , wordt 0xDF39 bijvoorbeeld als volgt berekend:

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

Hier volgt dezelfde berekening met decimale notatie:

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

In het voorgaande voorbeeld ziet u dat "\ud83c\udf39" de UTF-16-codering van het U+1F339 ROSE ('🌹') eerder genoemde codepunt is.

Unicode-scalaire waarden

De term Unicode-scalaire waarde verwijst naar alle andere codepunten dan de surrogaatcodepunten. Met andere woorden, een scalaire waarde is elk codepunt waaraan een character is toegewezen of die in de toekomst een character kan worden toegewezen. 'Teken' verwijst hier naar alles dat kan worden toegewezen aan een codepunt, waaronder zaken zoals acties die bepalen hoe tekst of characteerfuncties worden weergegeven.

In het volgende diagram ziet u de scalaire waardecodepunten.

Scalaire waarden

Het Rune type als scalaire waarde

Vanaf .NET Core 3.0 vertegenwoordigt het System.Text.Rune type een Unicode-scalaire waarde. Rune is niet beschikbaar in .NET Core 2.x of .NET Framework 4.x.

De Rune constructors valideren dat het resulterende exemplaar een geldige Unicode-scalaire waarde is, anders genereren ze een uitzondering. In het volgende voorbeeld ziet u code waarmee exemplaren worden geïnstitueert Rune omdat de invoer geldige scalaire waarden vertegenwoordigt:

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

In het volgende voorbeeld wordt een uitzondering gegenereerd omdat het codepunt zich in het surrogaatbereik bevindt en geen deel uitmaakt van een surrogaatpaar:

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

In het volgende voorbeeld wordt een uitzondering gegenereerd omdat het codepunt buiten het aanvullende bereik valt:

Rune g = new Rune(0x12345678);

Rune gebruiksvoorbeeld: hoofdletters wijzigen

Een API die een char api gebruikt en ervan uitgaat dat deze werkt met een codepunt dat een scalaire waarde is, werkt niet correct als de char api afkomstig is van een surrogaatpaar. Denk bijvoorbeeld aan de volgende methode die elke methode aanroept Char.ToUpperInvariant in eenstring:char

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

Als de inputstring kleine letter er Deseret (𐑉) bevat, wordt deze code niet geconverteerd naar hoofdletters (𐐡). De code roept char.ToUpperInvariant afzonderlijk aan op elk surrogaatcodepunt en U+D801U+DC49. Maar U+D801 er is niet voldoende informatie om deze als kleine letter te identificeren, dus char.ToUpperInvariant laat het alleen. En het handelt op dezelfde manier af U+DC49 . Het resultaat is dat kleine letters 𐑉niet inputstring worden geconverteerd naar hoofdletters '𐑉'.

Hier volgen twee opties voor het correct converteren van een string naar hoofdletter:

  • Roep String.ToUpperInvariant de invoer string aan in plaats van -by-techar herhalenchar. De string.ToUpperInvariant methode heeft toegang tot beide delen van elk surrogaatpaar, zodat alle Unicode-codepunten correct kunnen worden verwerkt.

  • Doorloop de Unicode-scalaire waarden als Rune exemplaren in plaats van char exemplaren, zoals wordt weergegeven in het volgende voorbeeld. Omdat een Rune exemplaar een geldige Unicode-scalaire waarde is, kan deze worden doorgegeven aan API's die verwachten te werken op een scalaire waarde. Aanroepen Rune.ToUpperInvariant zoals in het volgende voorbeeld wordt weergegeven, geven bijvoorbeeld de juiste resultaten:

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

Andere Rune API's

Het Rune type toont analogen van veel van de char API's. De volgende methoden spiegelen bijvoorbeeld statische API's voor het char type:

Gebruik de Rune.Value eigenschap om de onbewerkte scalaire waarde van een Rune exemplaar op te halen.

Als u een Rune exemplaar wilt converteren naar een reeks chars, gebruikt Rune.ToString u of de Rune.EncodeToUtf16 methode.

Aangezien een Unicode-scalaire waarde kan worden vertegenwoordigd door één char of door een surrogaatpaar, kan elk Rune exemplaar worden vertegenwoordigd door maximaal 2 char exemplaren. Gebruik Rune.Utf16SequenceLength dit om te zien hoeveel char exemplaren nodig zijn om een Rune exemplaar weer te geven.

Zie deRuneAPI-verwijzing voor meer informatie over het .NET-typeRune.

Grapheme-clusters

Wat eruitziet als één character kan het gevolg zijn van een combinatie van meerdere codepunten, dus een meer beschrijvende term die vaak wordt gebruikt in plaats van 'character', is een grapheme-cluster. De equivalente term in .NET is een tekstelement.

Bekijk de string exemplaren 'a', 'á', 'á' en '👩🏽‍🚒'. Als uw besturingssysteem deze verwerkt zoals opgegeven door de Unicode-standaard, wordt elk van deze string exemplaren weergegeven als één tekstelement of grapheme-cluster. Maar de laatste twee worden vertegenwoordigd door meer dan één scalaire waardecodepunt.

  • De string 'a' wordt vertegenwoordigd door één scalaire waarde en bevat één char exemplaar.

    • U+0061 LATIN SMALL LETTER A
  • De string 'á' wordt vertegenwoordigd door één scalaire waarde en bevat één char exemplaar.

    • U+00E1 LATIN SMALL LETTER A WITH ACUTE
  • De string 'á' ziet er hetzelfde uit als 'á', maar wordt vertegenwoordigd door twee scalaire waarden en bevat twee char exemplaren.

    • U+0061 LATIN SMALL LETTER A
    • U+0301 COMBINING ACUTE ACCENT
  • Ten slotte wordt de string '👩🏽‍🚒' vertegenwoordigd door vier scalaire waarden en bevat zeven char exemplaren.

    • U+1F469 WOMAN (aanvullend bereik vereist een surrogaatpaar)
    • U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4 (aanvullend bereik vereist een surrogaatpaar)
    • U+200D ZERO WIDTH JOINER
    • U+1F692 FIRE ENGINE (aanvullend bereik vereist een surrogaatpaar)

In sommige van de voorgaande voorbeelden, zoals de combinatie van accentaanpassing of de aanpassing van de huidtoon, wordt het codepunt niet weergegeven als een zelfstandig element op het scherm. In plaats daarvan dient het om het uiterlijk van een tekstelement te wijzigen dat ervoor kwam. Deze voorbeelden laten zien dat het meerdere scalaire waarden kan duren om samen te stellen wat we beschouwen als één 'character' of 'grapheme-cluster'.

Als u de grafemeclusters van een wilt stringinventariseren, gebruikt u de StringInfo klasse, zoals wordt weergegeven in het volgende voorbeeld. Als u bekend bent met Swift, is het .NET-type StringInfo conceptueel vergelijkbaar met het type van character Swift.

Voorbeeld: exemplaren van aantallen char, Runeen tekstelementen

In .NET API's wordt een grapheme-cluster een tekstelement genoemd. De volgende methode toont de verschillen tussen char, Runeen tekstelementexemplaren in een 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

Als u deze code uitvoert in .NET Framework of .NET Core 3.1 of eerder, wordt het aantal tekstelementen voor de emoji weergegeven 4. Dat komt door een fout in de StringInfo klasse die is opgelost in .NET 5.

Voorbeeld: exemplaren splitsen string

Vermijd bij het splitsen van string exemplaren het splitsen van surrogaatparen en grafemeclusters. Bekijk het volgende voorbeeld van onjuiste code, waarmee regeleinden elke tien characteerfuncties in een 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();
}

Omdat deze code exemplaren opsommen char , wordt een surrogaatpaar dat plaatsvindt om een grens van 10-char te splitsen en een nieuwe regel ertussen geïnjecteerd. Deze invoeging introduceert beschadiging van gegevens, omdat surrogaatcodepunten alleen zinvol zijn als paren.

Het potentieel voor beschadiging van gegevens wordt niet geëlimineerd als u exemplaren (scalaire waarden) opsommen Rune in plaats van char exemplaren. Een set Rune exemplaren kan een grapheme-cluster vormen dat een grens van 10char overschrijdt. Als de grapheme-clusterset is gesplitst, kan deze niet correct worden geïnterpreteerd.

Een betere benadering is om het string te breken door graafclusters of tekstelementen te tellen, zoals in het volgende voorbeeld:

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

}

Zoals eerder is opgemerkt, had de StringInfo klasse vóór .NET 5 een fout waardoor sommige grapheme-clusters onjuist worden verwerkt.

UTF-8 en UTF-32

De voorgaande secties zijn gericht op UTF-16, omdat dat is wat .NET gebruikt om instanties te coderen string . Er zijn andere coderingssystemen voor Unicode - UTF-8 en UTF-32. Deze coderingen gebruiken respectievelijk 8-bits code-eenheden en 32-bits code-eenheden.

Net als UTF-16 vereist UTF-8 meerdere code-eenheden om bepaalde Unicode-scalaire waarden weer te geven. UTF-32 kan elke scalaire waarde in één 32-bits code-eenheid vertegenwoordigen.

Hier volgen enkele voorbeelden die laten zien hoe hetzelfde Unicode-codepunt wordt weergegeven in elk van deze drie Unicode-coderingssystemen:

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)

Zoals eerder vermeld, is één UTF-16-code-eenheid van een surrogaatpaar op zichzelf betekenisloos. Op dezelfde manier is één UTF-8-code-eenheid op zichzelf betekenisloos als deze zich in een reeks van twee, drie of vier bevindt die wordt gebruikt om een scalaire waarde te berekenen.

Notitie

Vanaf C# 11 kunt u UTF-8 string letterlijke waarden weergeven met behulp van het achtervoegsel 'u8' op een letterlijke waarde string. Zie de sectie 'stringletterlijke gegevens' van het artikel over ingebouwde referentietypen in de C#-handleiding voor meer informatie over letterlijke UTF-8-gegevensstring.

Endianness

In .NET worden de UTF-16-code-eenheden van een bestand string opgeslagen in aaneengesloten geheugen als een reeks van 16-bits gehele getallen (char exemplaren). De bits van afzonderlijke code-eenheden worden ingedeeld op basis van de endianiteit van de huidige architectuur.

In een little-endian-architectuur wordt het string bestaande uit de UTF-16-codepunten [ D801 DCCC ] in het geheugen ingedeeld als de bytes [ 0x01, 0xD8, 0xCC, 0xDC ]. Op een big-endian-architectuur die hetzelfde string zou worden ingedeeld in het geheugen als de bytes [ 0xD8, 0x01, 0xDC, 0xCC ].

Computersystemen die met elkaar communiceren, moeten akkoord gaan met de weergave van gegevens die de draad overschrijden. De meeste netwerkprotocollen gebruiken UTF-8 als standaard bij het verzenden van tekst, deels om problemen te voorkomen die kunnen voortvloeien uit een big-endian-machine die communiceert met een little-endian-machine. Het string bestaande uit de UTF-8-codepunten [ F0 90 93 8C ] wordt altijd weergegeven als de bytes [ 0xF0, 0x90, 0x93, 0x8C ] , ongeacht de endianiteit.

Als u UTF-8 wilt gebruiken voor het verzenden van tekst, gebruiken .NET-toepassingen vaak code zoals in het volgende voorbeeld:

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

In het voorgaande voorbeeld codeert de methode Encoding.UTF8.GetBytes de UTF-16 string terug in een reeks Unicode-scalaire waarden. Vervolgens worden deze scalaire waarden opnieuw gecodeerd in UTF-8 en wordt de resulterende reeks in een byte matrix geplaatst. De methode Encoding.UTF8.GetString voert de tegenovergestelde transformatie uit, waarbij een UTF-8-matrix byte wordt geconverteerd naar een UTF-16 string.

Waarschuwing

Omdat UTF-8 gebruikelijk is op internet, kan het verleidelijk zijn om onbewerkte bytes van de draad te lezen en om de gegevens te behandelen alsof het UTF-8 was. U moet echter valideren dat deze inderdaad goed is gevormd. Een kwaadwillende client kan UTF-8 indienen bij uw service. Als u op die gegevens werkt alsof deze goed zijn gevormd, kunnen deze fouten of beveiligingsgaten in uw toepassing veroorzaken. Als u UTF-8-gegevens wilt valideren, kunt u een methode zoals Encoding.UTF8.GetString, waarmee validatie wordt uitgevoerd tijdens het converteren van de binnenkomende gegevens naar een string.

Goed gevormde codering

Een goed gevormde Unicode-codering is een string code-eenheden die ondubbelzinnig en zonder fouten kunnen worden gedecodeerd in een reeks Unicode-scalaire waarden. Goed gevormde gegevens kunnen vrij heen en weer worden getranscodeerd tussen UTF-8, UTF-16 en UTF-32.

De vraag of een coderingsreeks goed is gevormd of niet is gerelateerd aan de endianiteit van de architectuur van een machine. Een slecht gevormde UTF-8-reeks is op dezelfde manier gevormd op zowel big-endiane als little-endian machines.

Hier volgen enkele voorbeelden van slecht gevormde coderingen:

  • In UTF-8 is de sequentie [ 6C C2 61 ] ziek gevormd omdat C2 deze niet kan worden gevolgd door 61.

  • In UTF-16 is de reeks [ DC00 DD00 ] (of, in C#, de string"\udc00\udd00") ongeldig gevormd omdat het lage surrogaat DC00 niet kan worden gevolgd door een andere lage surrogaat DD00.

  • In UTF-32 is de reeks [ 0011ABCD ] ongeldig, omdat 0011ABCD deze zich buiten het bereik van Unicode-scalaire waarden bevindt.

In .NET string bevatten exemplaren bijna altijd goed opgemaakte UTF-16-gegevens, maar dat is niet gegarandeerd. In de volgende voorbeelden ziet u geldige C#-code waarmee ongeldige UTF-16-gegevens in string instanties worden gemaakt.

  • Een ziek gevormde letterlijke:

    const string s = "\ud800";
    
  • Een substring die een surrogaatpaar splitst:

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

API's retourneren nooit Encoding.UTF8.GetString slecht gevormde string instanties. Encoding.GetString en Encoding.GetBytes methoden detecteren slecht gevormde sequenties in de invoer en voeren actervervanging uit charbij het genereren van de uitvoer. Als Encoding.ASCII.GetString(byte[]) u bijvoorbeeld een niet-ASCII-byte in de invoer ziet (buiten het bereik U+0000..U+007F), wordt er een '?' ingevoegd in het geretourneerde string exemplaar. Encoding.UTF8.GetString(byte[]) vervangt slecht gevormde UTF-8-reeksen door U+FFFD REPLACEMENT CHARACTER ('�') in het geretourneerde string exemplaar. Zie de Unicode-standaard, secties 5.22 en 3.9 voor meer informatie.

De ingebouwde Encoding klassen kunnen ook worden geconfigureerd om een uitzondering te genereren in plaats van actervervanging uit te voeren charwanneer er ziek gevormde reeksen worden gezien. Deze benadering wordt vaak gebruikt in beveiligingsgevoelige toepassingen waarbij charhet vervangen van bewerkingen mogelijk niet acceptabel is.

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

Zie Acter-coderingsklassen gebruiken in .NET voor meer informatie over het gebruik charvan de ingebouwde Encoding klassen.

Zie ook