Codifica dei caratteri in .NET
Questo articolo fornisce un'introduzione ai sistemi di codifica dei caratteri usati da .NET. L'articolo illustra il funzionamento dei tipi String, Char, Rune e StringInfo con Unicode, UTF-16 e UTF-8.
Il termine carattere è usato qui nel senso generale di ciò che un lettore percepisce come un singolo elemento di visualizzazione. Esempi comuni sono la lettera "a", il simbolo "@" e l'emoji "🐂". A volte quello che sembra un carattere è in realtà composto da più elementi di visualizzazione indipendenti, come spiega la sezione sui cluster di grafemi.
I tipi string e char
Un'istanza della classe string rappresenta del testo. Una string
è a livello logico una sequenza di valori a 16 bit, ognuno dei quali è un'istanza dello struct char. La proprietà string.Length restituisce il numero di istanze di char
nell'istanza di string
.
La funzione di esempio seguente stampa i valori nella notazione esadecimale di tutte le istanze del char
in una 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();
}
Passare la string "Hello" a questa funzione per ottenere l'output seguente:
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')
Ogni carattere è rappresentato da un singolo char
valore. Questo modello vale per la maggior parte delle lingue del mondo. Ad esempio, ecco l'output di due caratteri cinesi che suonano come nǐ hǎo e significano Hello:
PrintChars("你好");
"你好".Length = 2
s[0] = '你' ('\u4f60')
s[1] = '好' ('\u597d')
Tuttavia, per alcune lingue e per alcuni simboli ed emoji, sono necessarie due char
istanze per rappresentare un singolo carattere. Ad esempio, confronta i caratteri e char
le istanze nella parola che significa Osage nella lingua 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')
Nell'esempio precedente, ogni carattere, ad eccezione dello spazio, è rappresentato da due char
istanze.
Anche un singolo emoji Unicode è rappresentato da due char
, come illustrato nell'esempio seguente che mostra l'emoji di un bue:
"🐂".Length = 2
s[0] = '�' ('\ud83d')
s[1] = '�' ('\udc02')
Questi esempi mostrano che il valore di string.Length
, che indica il numero di char
istanze, non indica necessariamente il numero di caratteri visualizzati. Una singola char
istanza di per sé non rappresenta necessariamente un carattere.
Le char
coppie mappate a un singolo carattere sono denominate coppie di surrogati. Per capire come funzionano, è necessario comprendere la codifica Unicode e UTF-16.
Punti di codice Unicode
Unicode è uno standard di codifica internazionale per l'uso in varie piattaforme e con vari linguaggi e script.
Lo standard Unicode definisce oltre 1,1 milioni di punti di codice. Un punto di codice è un valore intero che può variare da 0 a U+10FFFF
(1.114.111 in decimali). Alcuni punti di codice vengono assegnati a lettere, simboli o emoji. Altri sono assegnati ad azioni che controllano la modalità di visualizzazione del testo o dei caratteri, ad esempio l'avanzamento a una nuova riga. Molti punti di codice non sono stati ancora assegnati.
Di seguito sono riportati alcuni esempi di assegnazioni di punti di codice, con collegamenti ai grafici Unicode in cui vengono visualizzati:
Decimale | Hex | Esempio | Descrizione |
---|---|---|---|
10 | U+000A |
N/D | AVANZAMENTO RIGA |
97 | U+0061 |
a | LETTERE A LATINA MINUSCOLA |
562 | U+0232 |
Ȳ | LETTERA Y LATINA MAIUSCOLA CON SEGNO DI VOCALE LUNGA |
68.675 | U+10C43 |
𐱃 | VECCHIA LETTERA TURCA ORKHON AT |
127.801 | U+1F339 |
🌹 | Emoji di ROSA |
I punti di codice vengono definiti in genere usando la sintassi U+xxxx
, dove xxxx
è il valore intero con codifica esadecimale.
All'interno dell'intervallo completo di punti di codice esistono due intervalli secondari:
- Basic Multilingual Plane (BMP) nell'intervallo
U+0000..U+FFFF
. Questo intervallo a 16 bit prevede 65.536 punti di codice, un numero sufficiente per coprire la maggior parte dei sistemi di scrittura del mondo. - Punti di codice supplementari nell'intervallo
U+10000..U+10FFFF
. Questo intervallo a 21 bit prevede più di un milione di punti di codice aggiuntivi che possono essere usati per lingue meno noti e per altri scopi, ad esempio gli emoji.
Il diagramma seguente illustra la relazione tra il BMP e i punti di codice supplementari.
Unità di codice UTF-16
UTF-16 (16-bit Unicode Transformation Format) è un sistema di codifica dei caratteri che utilizza unità di codice a 16 bit per rappresentare i punti di codice Unicode. .NET usa UTF-16 per codificare il testo in una string
. Un'istanza di char
rappresenta un'unità di codice a 16 bit.
Una singola unità di codice a 16 bit può rappresentare qualsiasi punto di codice nell'intervallo a 16 bit del BMP. Tuttavia, per un punto di codice nell'intervallo supplementare, sono necessarie due istanze di char
.
Coppie di surrogati
La conversione di due valori a 16 bit in un singolo valore a 21 bit è facilitata da un intervallo speciale detto punti di codice surrogati, compreso tra U+D800
a U+DFFF
(tra 55.296 e 57.343 in decimali) inclusi.
Il diagramma seguente illustra la relazione tra il BMP e i punti di codice surrogati.
Quando un punto di codice surrogato alto (U+D800..U+DBFF
) è seguito immediatamente da un punto di codice surrogato basso (U+DC00..U+DFFF
), la coppia viene interpretata come un punto di codice supplementare tramite la formula seguente:
code point = 0x10000 +
((high surrogate code point - 0xD800) * 0x0400) +
(low surrogate code point - 0xDC00)
Ecco la stessa formula con la notazione decimale:
code point = 65,536 +
((high surrogate code point - 55,296) * 1,024) +
(low surrogate code point - 56,320)
Un punto di codice surrogato alto non ha un valore numerico superiore rispetto a un punto di codice surrogato basso. Il punto di codice surrogato alto è detto "alto" perché viene usato per calcolare i 10 bit di ordine superiore dell'intervallo di punti di codice a 20 bit. Il punto di codice surrogato basso viene usato per calcolare i 10 bit di ordine inferiore.
Ad esempio, il punto di codice effettivo che corrisponde alla coppia di surrogati 0xD83C
e 0xDF39
viene calcolato come segue:
actual = 0x10000 + ((0xD83C - 0xD800) * 0x0400) + (0xDF39 - 0xDC00)
= 0x10000 + ( 0x003C * 0x0400) + 0x0339
= 0x10000 + 0xF000 + 0x0339
= 0x1F339
Ecco lo stesso calcolo con la notazione decimale:
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
L'esempio precedente dimostra che "\ud83c\udf39"
è la codifica UTF-16 del punto di codice U+1F339 ROSE ('🌹')
menzionato in precedenza.
Valori scalari Unicode
Il termine valore scalare Unicode si riferisce a tutti i punti di codice diversi dai punti di codice surrogati. In altre parole, un valore scalare è qualsiasi punto di codice a cui viene assegnato un carattere o a cui può essere assegnato un carattere in futuro. "Carattere" qui si riferisce a tutto ciò che può essere assegnato a un punto di codice, che include elementi come le azioni che controllano il modo in cui il testo o i caratteri vengono visualizzati.
Il diagramma che segue illustra i punti di codice in valori scalari.
Il tipo Rune come valore scalare
Importante
Il Rune
tipo non è disponibile in .NET Framework.
In .NET il System.Text.Rune tipo rappresenta un valore scalare Unicode.
I costruttori Rune
verificano che l'istanza risultante sia un valore scalare Unicode valute e in caso contrario generano un'eccezione. L'esempio seguente mostra il codice che crea correttamente istanze di Rune
perché l'input rappresenta valori scalari validi:
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');
L'esempio seguente genera un'eccezione perché il punto di codice si trova nell'intervallo di surrogati e non fa parte di una coppia di surrogati:
Rune f = new Rune('\ud801');
L'esempio seguente genera un'eccezione perché il punto di codice non rientra nell'intervallo di supplementari:
Rune g = new Rune(0x12345678);
Esempio di utilizzo di Rune: modifica della combinazione di maiuscole/minuscole
Un'API che accetta un char
e presuppone che funzioni con un punto di codice che corrisponde a un valore scalare non funziona correttamente se il char
proviene da una coppia di surrogati. Si consideri, ad esempio, il metodo seguente che chiama Char.ToUpperInvariant in ogni char di una string:
// 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();
}
Se la string di input
contiene la lettera Deseret minuscola er
(𐑉
), questo codice non la converte in maiuscolo (𐐡
). Il codice chiama char.ToUpperInvariant
separatamente in ogni punto di codice surrogato, U+D801
e U+DC49
. But U+D801
non contiene di per sé informazioni sufficienti per identificare la lettera come minuscola, quindi char.ToUpperInvariant
non ha effetto. E gestisce U+DC49
allo stesso modo. Il risultato è che la lettera '𐑉' minuscola nella string di input
non viene convertita nella lettera '𐐡' maiuscola.
Ecco due opzioni per convertire correttamente una string in maiuscolo:
Chiamare String.ToUpperInvariant nella string di input invece di eseguire l'iterazione
char
perchar
. Il metodostring.ToUpperInvariant
ha accesso a entrambe le parti di ogni coppia di surrogati, quindi può gestire tutti i punti di codice Unicode correttamente.Eseguire l'iterazione attraverso i valori scalari Unicode come istanze di
Rune
invece di istanze dichar
, come illustrato nell'esempio seguente. Poiché un'istanza diRune
è un valore scalare Unicode valido, può essere passata alle API che prevedono di operare su un valore scalare. Ad esempio, la chiamata a Rune.ToUpperInvariant come illustrato nell'esempio seguente restituisce risultati corretti: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(); }
Altre API Rune
Il tipo Rune
espone analogie di molte API di char
. Ad esempio, i metodi seguenti rispecchiano le API statiche del tipo char
:
Per ottenere il valore scalare non elaborato da un'istanza di Rune
, usare la proprietà Rune.Value.
Per riconvertire un'istanza di Rune
in una sequenza di char
, usare il metodo Rune.ToString o Rune.EncodeToUtf16.
Poiché qualsiasi valore scalare Unicode può essere rappresentato da un singolo char
o da una coppia di surrogati, qualsiasi istanza di Rune
può essere rappresentata al massimo da 2 istanze di char
. Usare Rune.Utf16SequenceLength per verificare il numero di istanze di char
necessarie per rappresentare un'istanza di Rune
.
Per altre informazioni ed esempi sul tipo Rune
.NET, vedere le informazioni di riferimento sull'APIRune
.
Cluster di grafemi
Quello che sembra un carattere potrebbe derivare da una combinazione di più punti di codice, quindi un termine più descrittivo che viene spesso utilizzato al posto di "carattere" è cluster di grafemi. Il termine equivalente in .NET è elemento di testo.
Si considerino le istanze di string
"a", "á", "á" e "👩🏽🚒
". Se il sistema operativo le gestisce come specificato dallo standard Unicode, ognuna di queste istanze di string
viene visualizzata come un singolo elemento di testo o cluster di grafemi. Ma le ultime due sono rappresentate da più di un punto di codice di valore scalare.
La string "a" è rappresentata da un valore scalare e contiene un'istanza di
char
.U+0061 LATIN SMALL LETTER A
La string "á" è rappresentata da un valore scalare e contiene un'istanza di
char
.U+00E1 LATIN SMALL LETTER A WITH ACUTE
La string "á" sembra uguale ad "á", ma è rappresentata da due valori scalari e contiene due istanze di
char
.U+0061 LATIN SMALL LETTER A
U+0301 COMBINING ACUTE ACCENT
Infine la string "
char
" è rappresentata da quattro valori scalari e contiene sette istanze di👩🏽🚒
.U+1F469 WOMAN
(intervallo di supplementari, richiede una coppia di surrogati)U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4
(intervallo di supplementari, richiede una coppia di surrogati)U+200D ZERO WIDTH JOINER
U+1F692 FIRE ENGINE
(intervallo di supplementari, richiede una coppia di surrogati)
In alcuni degli esempi precedenti, ad esempio il modificatore combining accent o il modificatore skin tone, il punto di codice non viene visualizzato come elemento autonomo sullo schermo. Serve piuttosto per modificare l'aspetto di un elemento di testo che lo precede. Questi esempi mostrano che potrebbero essere necessari più valori scalari per creare ciò che consideriamo un singolo "carattere" o "cluster di grafemi".
Per enumerare i cluster di grafemi di una string
, usare la classe StringInfo come illustrato nell'esempio seguente. Se si ha familiarità con Swift, il tipo StringInfo
.NET è concettualmente simile al tipo character
di Swift.
Esempio: conteggio di istanze di char, di Rune e di elementi di testo
Nelle API .NET un cluster di grafemi è detto elemento di testo. Il metodo seguente illustra le differenze tra le istanze di char
, di Rune
e di elementi di testo in una 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
Esempio: divisione di istanze di string
Quando si dividono le istanze di string
, evitare di dividere coppie di surrogati e cluster di grafemi. Si consideri il seguente esempio di codice non corretto, che intende inserire interruzioni di riga ogni 10 caratteri in un 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();
}
Poiché questo codice enumera le istanze di char
, una coppia di surrogati che supera un limite di 10 char
verrà divisa e tra di essi verrà inserita una nuova riga. Questo inserimento introduce il danneggiamento dei dati, perché i punti di codice surrogati sono significativi solo come coppie.
Il rischio di danneggiamento dei dati non viene eliminato se si enumerano le istanze di Rune
(valori scalari) invece delle istanze di char
. Un set di istanze di Rune
può creare un cluster di grafemi che supera un limite di 10 char
. Se il cluster di grafemi viene diviso, non può essere interpretato correttamente.
Un approccio migliore consiste nell'interrompere la string contando i cluster di grafemi, o elementi di testo, come nell'esempio seguente:
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();
}
Come indicato in precedenza, prima di .NET 5, la classe StringInfo
presentava un bug che causava la gestione non corretta di alcuni cluster di grafemi.
UTF-8 e UTF-32
Le sezioni precedenti sono incentrate su UTF-16 perché è il sistema usato da .NET per codificare le istanze di string
. Esistono altri sistemi di codifica per Unicode: UTF-8 e UTF-32. Queste codifiche usano rispettivamente unità di codice a 8 bit e unità di codice a 32 bit.
Analogamente a UTF-16, UTF-8 richiede più unità di codice per rappresentare alcuni valori scalari Unicode. UTF-32 può rappresentare qualsiasi valore scalare in una singola unità di codice a 32 bit.
Ecco alcuni esempi che illustrano come viene rappresentato lo stesso punto di codice Unicode in ognuno di questi tre sistemi di codifica 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)
Come indicato in precedenza, una singola unità di codice UTF-16 di una coppia di surrogati non è significativa di per sé. Allo stesso modo, una singola unità di codice UTF-8 non è significativa di per sé se è inserita in una sequenza di due, tre o quattro usata per calcolare un valore scalare.
Nota
A partire da C# 11, è possibile rappresentare valori letterali string UTF-8 usando il suffisso "u8" in un valore letterale string. Per altre informazioni sui valori letterali string UTF-8, vedere la sezione "Valori letterali string" dell'articolo sui tipi di riferimento predefiniti nella Guida di C#.
Ordine dei byte
In .NET le unità di codice UTF-16 di una string vengono archiviate nella memoria contigua come sequenza di interi a 16 bit (istanze di char
). I bit di singole unità di codice vengono disposti in base all'ordine dei byte dell'architettura corrente.
In un'architettura little-endian, la string costituita da punti di codice UTF-16 [ D801 DCCC ]
verrebbe disposta in memoria come byte [ 0x01, 0xD8, 0xCC, 0xDC ]
. In un'architettura big-endian la string verrebbe disposta in memoria come byte [ 0xD8, 0x01, 0xDC, 0xCC ]
.
I sistemi informatici che comunicano tra loro devono concordare la rappresentazione dei dati che trasmettono. La maggior parte dei protocolli di rete usa UTF-8 come standard durante la trasmissione del testo, in parte per evitare problemi che potrebbero derivare da un computer big-endian che comunica con un computer little-endian. La string costituita dai punti di codice UTF-8 [ F0 90 93 8C ]
verrà sempre rappresentata come byte [ 0xF0, 0x90, 0x93, 0x8C ]
indipendentemente dall'ordine di byte.
Per usare UTF-8 per la trasmissione di testo, le applicazioni .NET usano spesso codice simile all'esempio seguente:
string stringToWrite = GetString();
byte[] stringAsUtf8Bytes = Encoding.UTF8.GetBytes(stringToWrite);
await outputStream.WriteAsync(stringAsUtf8Bytes, 0, stringAsUtf8Bytes.Length);
Nell'esempio precedente, il metodo Encoding.UTF8.GetBytes decodifica la string
UTF-16 in una serie di valori scalari Unicode, quindi ricodifica tali valori scalari in UTF-8 e inserisce la sequenza risultante in una matrice di byte
. Il metodo Encoding.UTF8.GetString esegue la trasformazione opposta, convertendo una matrice di byte
UTF-8 in una string
UTF-16.
Avviso
Poiché UTF-8 è di uso comune su Internet, si potrebbe essere tentati di leggere byte non elaborati dalla trasmissione e di trattare i dati come se fossero UTF-8. Tuttavia, è necessario verificare che i dati siano ben formati. Un client malintenzionato potrebbe inviare un UTF-8 non ben formato al servizio. Se si opera su tali dati come se fossero ben formati, potrebbero verificarsi errori o problemi di sicurezza nell'applicazione. Per convalidare i dati UTF-8, è possibile usare un metodo come Encoding.UTF8.GetString
, che eseguirà la convalida durante la conversione dei dati in ingresso in una string
.
Codifica ben formata
Una codifica Unicode ben formata è una string di unità di codice che può essere decodificata in modo non ambiguo e senza errori in una sequenza di valori scalari Unicode. I dati ben formati possono essere transcodificati liberamente tra UTF-8, UTF-16 e UTF-32.
La questione se una sequenza di codifica è ben formata o meno non è correlata all'ordine di byte dell'architettura di un computer. Una sequenza UTF-8 è non ben formata allo stesso modo sia nei computer big-endian sia little-endian.
Ecco alcuni esempi di codifiche non ben formate:
In UTF-8 la sequenza
[ 6C C2 61 ]
non è ben formata perchéC2
non può essere seguito da61
.In UTF-16 la sequenza
[ DC00 DD00 ]
(o, in C#, la string"\udc00\udd00"
) non è ben formata perché il surrogato bassoDC00
non può essere seguito da un altro surrogato bassoDD00
.In UTF-32 la sequenza
[ 0011ABCD ]
non è ben formata perché0011ABCD
non è compreso nell'intervallo di valori scalari Unicode.
In .NET le istanze di string
contengono quasi sempre dati UTF-16 ben formati, ma ciò non è garantito. Gli esempi seguenti mostrano codice C# valido che crea dati UTF-16 non ben formati nelle istanze di string
.
Un valore letterale non ben formato:
const string s = "\ud800";
Una sottostringa che suddivide una coppia di surrogati:
string x = "\ud83e\udd70"; // "🥰" string y = x.Substring(1, 1); // "\udd70" standalone low surrogate
Le API come Encoding.UTF8.GetString
non restituiscono mai istanze di string
non ben formate. Encoding.GetString
e Encoding.GetBytes
i metodi rilevano sequenze mal formate nell'input ed eseguono la sostituzione dei caratteri durante la generazione dell'output. Ad esempio, se Encoding.ASCII.GetString(byte[])
rileva un byte ASCII nell'input (non compreso nell'intervallo U+0000..U+007F), inserisce un carattere '?' nell'istanza di string
restituita. Encoding.UTF8.GetString(byte[])
sostituisce le sequenze UTF-8 non ben formate con U+FFFD REPLACEMENT CHARACTER ('�')
nell'istanza di string
restituita. Per altre informazioni, vedere Standard Unicode, sezioni 5.22 e 3.9.
Le classi predefinite Encoding
possono anche essere configurate per generare un'eccezione anziché eseguire la sostituzione dei caratteri quando vengono rilevate sequenze non formate. Questo approccio viene spesso utilizzato in applicazioni sensibili alla sicurezza in cui la sostituzione dei caratteri potrebbe non essere accettabile.
byte[] utf8Bytes = ReadFromNetwork();
UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
string asString = encoding.GetString(utf8Bytes); // will throw if 'utf8Bytes' is ill-formed
Per informazioni su come usare le classi predefinite Encoding
, vedere Come usare le classi di codifica dei caratteri in .NET.