Condividi tramite


Codifica dei caratteri in .NET

Questo articolo fornisce un'introduzione ai sistemi di codifica di char usati in .NET. L'articolo illustra il funzionamento dei tipi String, Char, Rune e StringInfo con Unicode, UTF-16 e UTF-8.

Il termine char viene usato qui nel senso generale di ciò che un lettore percepisce come un singolo elemento visualizzato. Esempi comuni sono la lettera "a", il simbolo "@" e l'emoji "🐂". A volte quello che sembra un unico char è in realtà costituito da più elementi visualizzati indipendenti, come illustrato nella 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 char è rappresentato da un singolo valore char. Questo modello vale per la maggior parte delle lingue del mondo. Ad esempio, ecco l'output per due char cinesi che vengono pronunciati 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 istanze di char per rappresentare un singolo char. Ad esempio, confrontare i char e le istanze di char nella parola che significa Osage nella lingua degli 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 char tranne lo spazio è rappresentato da due istanze di char.

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 dimostrano che il valore di string.Length, che indica il numero di istanze di char, non indica necessariamente il numero di char visualizzati. Una singola istanza di char di per sé non rappresenta necessariamente un char.

Le coppie di char mappate a un singolo char sono dette 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 vengono assegnati alle azioni che controllano la modalità di visualizzazione del testo o di char, ad esempio l'avanzamento a una nuova riga. Molti punti di codice non sono stati ancora assegnati.

Ecco alcuni esempi di assegnazioni di punti di codice, con collegamenti ai char Unicode in cui compaiono:

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.

BMP e punti di codice supplementari

Unità di codice UTF-16

Il formato UTF-16 (Unicode Transformation Format) a 16 bit è un sistema di codifica di char che usa 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.

BMP e 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 char o a cui può essere assegnato un char in futuro. Per "carattere" qui si intende qualsiasi cosa che possa essere assegnata a un punto di codice, tra cui le azioni che controllano la modalità di visualizzazione di testo o char.

Il diagramma che segue illustra i punti di codice in valori scalari.

Valori scalari

Il tipo Rune come valore scalare

A partire da .NET Core 3.0, il tipo System.Text.Rune rappresenta un valore scalare Unicode. Rune non è disponibile .NET Core 2.x o in .NET Framework 4.x.

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 per char. Il metodo string.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 di char, come illustrato nell'esempio seguente. Poiché un'istanza di Rune è 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 char può derivare da una combinazione di più punti di codice, quindi un termine più descrittivo usato al posto di "char" è 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 quello che si ritiene essere un singolo "char" 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

Se si esegue questo codice in .NET Framework o in .NET Core 3.1 o versione precedente, il conteggio degli elementi di testo per l'emoji è 4. Ciò è dovuto a un bug nella classe StringInfo che è stato corretto in .NET 5.

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 l'esempio seguente di codice non corretto, che prevede di inserire interruzioni di riga ogni 10 char in una 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 da 61.

  • In UTF-16 la sequenza [ DC00 DD00 ] (o, in C#, la string"\udc00\udd00") non è ben formata perché il surrogato basso DC00 non può essere seguito da un altro surrogato basso DD00.

  • 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 sottostring che divide 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. I metodi Encoding.GetString e Encoding.GetBytesrilevano le sequenze non ben formate nell'input ed eseguono la sostituzione di char 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 Encoding predefinite possono anche essere configurate per generare un'eccezione anziché eseguire la sostituzione di char quando vengono rilevate sequenze non ben formate. Questo approccio viene spesso usato nelle applicazioni sensibili alla sicurezza in cui la sostituzione di char 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 Encoding predefinite, vedere Come usare le classi Encoding di char .NET.

Vedi anche