1. Introduzione
È ora di discutere quelle situazioni fastidiose in cui lavorare con i file si trasforma in un incontro con errori (a volte piuttosto misteriosi). Se avete mai incontrato eccezioni come System.Text.DecoderFallbackException, allora conoscete già bene l'argomento!
In questa lezione vedremo:
- Quali tipi di errori legati alle codifiche esistono in .NET;
- Come si manifestano file corrotti o non corretti;
- Esempi pratici di intercettazione e gestione di questi errori;
- Su cosa fare attenzione quando lavorate con file altrui (o con i “vecchi e buoni” file trovati su un disco antico).
Quindi, se ASCII era troppo semplice e Unicode troppo intelligente, a volte si incontrano file che nessuno sa leggere. Ed è qui che spuntano le eccezioni.
Perché succede?
Quando aprite un file con StreamReader, specificando una codifica (o usando quella di default), .NET presume che tutti i byte nel file possano essere correttamente convertiti in caratteri. Ma se il file contiene byte che, nella codifica scelta, non corrispondono a nessun carattere, si verifica un errore di decodifica.
2. Eccezioni durante la lettura di file con codifica errata
L'eccezione più comune — DecoderFallbackException
Questa eccezione è sollevata da .NET quando non riesce a mappare una sequenza di byte su un carattere nella codifica prevista.
Esempio semplice, per chiarire:
// Supponiamo un vecchio file in Windows-1251 (cirillico)
string win1251File = "win1251_test.txt";
File.WriteAllText(win1251File, "Privet, mir!", Encoding.GetEncoding("windows-1251"));
try
{
// Proviamo a leggere questo file come UTF-8
using var reader = new StreamReader(win1251File, Encoding.UTF8);
string content = reader.ReadToEnd();
Console.WriteLine(content); // ...e stamperà caratteri illeggibili (o lancerà un'eccezione)
}
catch (DecoderFallbackException ex)
{
Console.WriteLine("Errore di decodifica: " + ex.Message);
}
Nella maggior parte dei casi, leggendo un file salvato in Windows-1251 come UTF-8, invece di testo sensato otterrete una serie di “caratteri strani”. Di default StreamReader in queste situazioni non lancia un'eccezione, ma inserisce il carattere di sostituzione "�" al posto dei byte incomprensibili. Tuttavia, se si imposta esplicitamente una codifica con un DecoderExceptionFallback rigido o se nello stream ci sono byte particolarmente “indigeribili”, verrà lanciata la DecoderFallbackException.
DecoderFallbackException in dettaglio
- Quando si verifica: quando si tenta di leggere una sequenza di byte che non può essere convertita in caratteri con la codifica corrente.
- Cosa fare: leggere il file con la codifica corretta! Se non sapete quale codifica ha il file, provate a indovinarla (a volte potete basarvi sul BOM o sul nome del file) oppure chiedete a chi ha creato il file.
3. Esempio con file chiaramente corrotto
Complichiamo un po' le cose. Immaginate che il file sia corrotto: dentro la sequenza di byte ci sono frammenti di caratteri troncati. Succede quando la scrittura del file è stata interrotta, per errori di rete, conversioni sbagliate o per un coltello da carta... letteralmente: il file è stato “tagliato” a caso.
Creiamo un file “rotto”
// Scriviamo una stringa valida in UTF-8
byte[] valid = Encoding.UTF8.GetBytes("Privet, mir!");
// Ora creiamo un array di byte sbagliato (tronchiamo parte di un carattere)
byte[] corrupted = new byte[valid.Length - 1];
Array.Copy(valid, corrupted, valid.Length - 1); // Abbiamo tolto l'ultimo byte
// Salviamo il file
File.WriteAllBytes("corrupted.txt", corrupted);
try
{
using var reader = new StreamReader("corrupted.txt", Encoding.UTF8);
string s = reader.ReadToEnd();
Console.WriteLine("Testo letto: " + s);
}
catch (DecoderFallbackException ex)
{
Console.WriteLine("File corrotto! " + ex.Message);
}
In uscita: .NET non riuscirà a ricostruire correttamente l'ultimo carattere. Di default lo sostituirà con il carattere speciale "�" (o con "?") oppure, se la codifica è configurata di conseguenza, lancerà la DecoderFallbackException.
4. Strategie di fallback: si può evitare l'eccezione?
A volte, quando un carattere è “incomprensibile”, si preferisce non interrompere l'esecuzione ma sostituirlo con “?” o con qualcos'altro. Per questo in .NET ci sono le cosiddette strategie di fallback.
Esempio: usiamo la sostituzione del carattere invece dell'eccezione
// Array con una sequenza non valida per UTF-8
byte[] data = { 0xD0, 0x9F, 0xD1, 0x80, 0xD0, 0xB8, 0xD0, 0xB2, 0xD0, 0xB5, 0xD1, 0x82, 0xD1 }; // L'ultimo byte è troncato
File.WriteAllBytes("broken_utf8.txt", data);
// Strategia di fallback: sostituire il carattere problematico con un punto interrogativo
var encodingWithFallback = Encoding.GetEncoding(
"UTF-8",
new EncoderReplacementFallback("?"),
new DecoderReplacementFallback("?")
);
using var reader = new StreamReader("broken_utf8.txt", encodingWithFallback);
string s = reader.ReadToEnd();
Console.WriteLine("Testo (con sostituzione degli errori): " + s);
Risultato: dal file verrà letto testo con i caratteri sconosciuti sostituiti da "?". In questo modo evitate il crash del programma, ma non otterrete il testo “nativo” completo.
5. Problemi con il BOM e incompatibilità
Ricordiamo che BOM è il Byte Order Mark, una sequenza speciale di byte all'inizio del file che dice “ciao, sono di questa codifica!”.
Quando il BOM può dare fastidio
- Se il file contiene un BOM e l'applicazione non sa leggerlo, il primo carattere della stringa sarà strano (per esempio "" o un carattere invisibile).
- A volte l'assenza del BOM porta a una determinazione errata della codifica.
Eccezioni correlate al BOM
Di solito C# sa “digerire” il BOM durante la lettura, ma se si specifica una codifica sbagliata o si rimuove manualmente il BOM, rischiate di ottenere:
- Un carattere inaspettato all'inizio (per esempio il carattere "�");
- Un'eccezione se la codifica è configurata per sollevare un errore e il BOM viene considerato una sequenza di byte non valida.
Consiglio pratico: specificate sempre esplicitamente la codifica durante lettura/scrittura se per voi è importante il tipo di codifica.
6. Altre eccezioni e scenari interessanti
Codifica sbagliata durante la scrittura
Quando provate a scrivere una stringa che contiene caratteri non supportati dalla codifica scelta. Per esempio, provate a salvare l'emoji “: )” (qui traslitterato) “😊” in un file con Encoding.ASCII:
try
{
using var writer = new StreamWriter("ascii.txt", false, Encoding.ASCII);
writer.WriteLine("Questo è un test 😊");
}
catch (EncoderFallbackException ex)
{
Console.WriteLine("Errore di codifica: " + ex.Message);
}
Risultato: otterrete o l'eccezione EncoderFallbackException, oppure il carattere verrà sostituito con "?" — dipende dalla strategia di fallback della codifica scelta.
Problema nella conversione tra codifiche (perdita di dati)
Durante la conversione di file potete involontariamente perdere parte dei dati se nella codifica di destinazione non ci sono tutti i caratteri della sorgente (per esempio, convertire da UTF-8 a Windows-1251 un file che contiene testo giapponese).
File corrotti da disco, rete o “editing manuale”
Se nel file finiscono byte casuali o corrotti (per esempio dopo un guasto del disco o aver aperto un file binario con un editor di testo), tentare di leggere quel file spesso lancerà eccezioni di decodifica.
7. Come intercettare e gestire gli errori nella pratica?
Dato che gli errori possono verificarsi in varie fasi della lavorazione dei file, è consigliabile:
- Usare blocchi try-catch per catturare le eccezioni — in primo luogo DecoderFallbackException e EncoderFallbackException.
- Non avere paura di informare l'utente: se il file è corrotto o la codifica è sbagliata — è meglio dirlo che mostrare testo strano.
- Automatizzare, se possibile, il rilevamento della codifica (per esempio tramite BOM o usando librerie come Ude), ma permettere sempre all'utente di scegliere una codifica alternativa in caso di insuccesso.
Struttura tipica del codice:
try
{
using var reader = new StreamReader("file.txt", Encoding.GetEncoding("windows-1251"));
string s = reader.ReadToEnd();
Console.WriteLine(s);
}
catch (DecoderFallbackException ex)
{
Console.WriteLine($"Impossibile leggere il file: {ex.Message}");
// Si può suggerire all'utente di provare un'altra codifica
}
catch (IOException ex)
{
Console.WriteLine($"Errore di I/O: {ex.Message}");
}
8. Qualche "trappola" tipica
Tentare di leggere un file UTF-8 come Windows-1251: nel migliore dei casi vedrete “caratteri strani”, nel peggiore otterrete un'eccezione (se la codifica è impostata per lanciarla).
Scrivere in ASCII un file con testo russo: tutto ciò che non è alfabeto inglese verrà sostituito con "?" o genererà EncoderFallbackException.
Leggere un file senza BOM come UTF-8 quando in realtà è UTF-16: otterrete roba incomprensibile o potreste non riuscire a leggere il file affatto.
File senza codifica esplicita provenienti da fonti non sicure: state sempre in guardia: anche se il file si apre “senza errori”, non è garanzia che il contenuto sia corretto.
GO TO FULL VERSION