Tekencodering in .NET
Dit artikel bevat een inleiding tot coderingssystemen voor tekens die worden gebruikt door .NET. In het artikel wordt uitgelegd hoe de Stringtypen CharUnicode StringInfo Rune, UTF-16 en UTF-8 werken.
Het termsteken 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 teken uit meerdere onafhankelijke weergave-elementen, zoals in de sectie over grafemeclusters 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')
Elk teken 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 tekens die als nǐ hǎo klinken en hallo betekenen:
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 teken weer te geven. Vergelijk bijvoorbeeld de tekens 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 elk teken, behalve de spatie, vertegenwoordigd door twee char
exemplaren.
Een enkele Unicode-emoji wordt ook vertegenwoordigd door twee char
s, 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 tekens aangeeft. char
Eén exemplaar op zichzelf vertegenwoordigt niet noodzakelijkerwijs een teken.
De char
paren die aan één teken zijn 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 tekens worden weergegeven, zoals naar een nieuwe regel gaan. Veel codepunten zijn nog niet toegewezen.
Hier volgen enkele voorbeelden van codepunttoewijzingen, met koppelingen naar Unicode-grafieken waarin 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.
UTF-16-code-eenheden
16-bits Unicode Transformation Format (UTF-16) is een systeem voor tekencodering 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.
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 teken is toegewezen of die in de toekomst een teken kan worden toegewezen. 'Teken' verwijst hier naar alles wat kan worden toegewezen aan een codepunt, waaronder bijvoorbeeld acties die bepalen hoe tekst of tekens worden weergegeven.
In het volgende diagram ziet u de scalaire waardecodepunten.
Het Rune type als scalaire waarde
Belangrijk
Het Rune
type is niet beschikbaar in .NET Framework.
In .NET vertegenwoordigt het System.Text.Rune type een Unicode-scalaire waarde.
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 input
string kleine letter er
Deseret (𐑉
) bevat, wordt deze code niet geconverteerd naar hoofdletters (𐐡
). De code roept char.ToUpperInvariant
afzonderlijk aan op elk surrogaatcodepunt en U+D801
U+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 input
string 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-te
char
herhalenchar
. Destring.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 vanchar
exemplaren, zoals wordt weergegeven in het volgende voorbeeld. Omdat eenRune
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 char
s, 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 deRune
API-verwijzing voor meer informatie over het .NET-typeRune
.
Grapheme-clusters
Wat eruitziet als één teken kan het gevolg zijn van een combinatie van meerdere codepunten, dus een meer beschrijvende term die vaak wordt gebruikt in plaats van 'teken' 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 zevenchar
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 bevatten om samen te stellen wat we beschouwen als één 'teken' of 'grapheme-cluster'.
Als u de grafemeclusters van een wilt string
inventariseren, 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
, Rune
en 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
Voorbeeld: exemplaren splitsen string
Vermijd bij het splitsen van string
exemplaren het splitsen van surrogaatparen en grafemeclusters. Bekijk het volgende voorbeeld van onjuiste code, die elke 10 tekens regeleinden in een stringregeleinden wilt invoegen:
// 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 omdatC2
deze niet kan worden gevolgd door61
.In UTF-16 is de reeks
[ DC00 DD00 ]
(of, in C#, de string"\udc00\udd00"
) ongeldig gevormd omdat het lage surrogaatDC00
niet kan worden gevolgd door een andere lage surrogaatDD00
.In UTF-32 is de reeks
[ 0011ABCD ]
ongeldig, omdat0011ABCD
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 subtekenreeks 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 ziek gevormde sequenties in de invoer en voeren vervanging van tekens uit bij 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 het vervangen van tekens uit te voeren wanneer er slecht gevormde reeksen worden gezien. Deze benadering wordt vaak gebruikt in beveiligingsgevoelige toepassingen waarbij het vervangen van tekens 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 Voor meer informatie over het gebruik van de ingebouwde Encoding
klassen, het gebruik van tekencoderingsklassen in .NET.