1. Classe File: semplicità ed efficienza
La classe System.IO.File incarna il concetto di "una operazione — un metodo". Ti offre una serie di metodi di alto livello che incapsulano la logica complessa dell'interazione con il sistema operativo, così puoi concentrarti solo sul tuo task.
Cosa può fare esattamente la classe File?
- Scrivere testo o dati binari arbitrari in un file.
- Leggere il contenuto di un file tutto insieme o riga per riga (per i file di testo).
- Copiare, eliminare e rinominare file esistenti.
- Verificare l'esistenza di un file in un percorso specificato.
- Lavorare con gli attributi del file (ad esempio, data di creazione, dimensione – tramite classi correlate).
Perché è comodo?
Tutti i metodi di File sono statici. Questo significa che non devi creare un oggetto per usarli, basta chiamare il metodo che ti serve, tipo:
File.WriteAllText("data.txt", "Ciao, file!");
Questo stile è molto simile a come si lavora con la classe Console: tutto semplice e chiaro, non devi "creare" nulla, non devi preoccuparti dei flussi.
Se vuoi avere uno strumento universale e comodo per lavorare con i file, la classe System.IO.File è proprio quello che fa per te. Offre un sacco di metodi per leggere, scrivere, copiare, eliminare e fare tutte le operazioni base sui file, come un coltellino svizzero per il filesystem.
Come si usa File?
Tutti i metodi della classe File sono statici, quindi non serve creare un oggetto per usarli — basta scrivere File.Qualcosa(). È molto simile alle chiamate tipo Console.WriteLine(), solo che ora lavori con i file.
string text = "Ciao, mondo!";
string path = "hello.txt";
// Scrivi testo su file (se il file esiste, lo sovrascrive!)
File.WriteAllText(path, text);
// Leggi tutto il file in una stringa
string readText = File.ReadAllText(path);
Console.WriteLine("Contenuto del file: " + readText);
Trucchetto: Se scrivi ReadAllText e WriteAllText dieci volte di fila, probabilmente stai facendo qualcosa di sbagliato — questi metodi vanno bene per scenari semplici e file piccoli.
Metodi più usati della classe File
| Metodo | Descrizione |
|---|---|
|
Controlla se esiste un file nel percorso specificato |
|
Elimina il file |
|
Copia il file |
|
Sposta (o rinomina) il file |
|
Legge tutto il file come una stringa unica |
|
Scrive una stringa su file (sovrascrive se già esiste) |
|
Legge tutte le righe del file in un array di stringhe |
|
Scrive un array di stringhe su file (ogni stringa è una riga) |
|
Legge tutti i byte del file in un array di byte |
|
Scrive un array di byte su file |
2. Scrivere e leggere file di testo
Vediamo i metodi più usati della classe File con esempi pratici. Per usarli serve sempre la direttiva using System.IO; all'inizio del tuo file di codice.
using System;
using System.IO; // Necessario per lavorare con la classe File
using System.Linq; // Può servire per ReadLines
Leggere un file intero
Questa è una delle cose che si fanno più spesso: salvare dati testuali su file o caricarli di nuovo.
Scrivere una riga (o tutto il testo) su file: File.WriteAllText() Questo metodo crea un nuovo file nel percorso specificato. Se esiste già un file con quel nome, il suo contenuto verrà completamente sovrascritto dal nuovo testo.
string myFilePath = "notes.txt";
string textContent = "Oggi è una giornata perfetta per imparare C#.\nNon dimenticare di fare i compiti.";
// Scriviamo tutto il testo su file
File.WriteAllText(myFilePath, textContent);
Console.WriteLine($"File '{myFilePath}' creato/sovrascritto con successo.");
Leggere tutto il file in una stringa: File.ReadAllText() Questo metodo legge tutto il contenuto del file di testo dall'inizio alla fine e lo restituisce come una stringa unica.
string myFilePath = "notes.txt";
// Importante: controlla sempre se il file esiste prima di leggerlo, per evitare errori!
if (File.Exists(myFilePath))
{
string content = File.ReadAllText(myFilePath);
Console.WriteLine($"Contenuto del file '{myFilePath}':\n{content}");
}
else
{
Console.WriteLine($"Errore: File '{myFilePath}' non trovato per la lettura.");
}
Nota importante: I metodi File.WriteAllText() e File.ReadAllText() sono perfetti per file piccoli e medi (tipo note di testo, file di configurazione, log brevi). Ma se il file è molto grande (diciamo, decine o centinaia di megabyte, o peggio gigabyte), caricare tutto il contenuto in una stringa in RAM può consumare un sacco di memoria e persino causare una OutOfMemoryException. In questi casi ci sono approcci più efficienti (ne parliamo più avanti e nelle prossime lezioni).
3. Leggere un file riga per riga
Quando lavori con file di testo grandi, tipo report CSV enormi, file di log lunghi o database testuali, leggerli tutti in memoria (cioè caricare tutto il file dal disco in RAM) diventa inefficiente o addirittura impossibile. Qui ti salva File.ReadLines().
File.ReadLines(path) — questo metodo non carica tutto il file subito. Invece, restituisce una collezione speciale (IEnumerable<string>) che ti permette di leggere il file riga per riga quando ti serve. Si chiama "lettura lazy" o "lettura in streaming".
string logFilePath = "application.log";
string text = """
INFO: Applicazione avviata.
WARN: Problema rilevato nel modulo X.
ERROR: Errore critico di rete.
INFO: Applicazione terminata.
""";
// Supponiamo di avere un file di log con tante righe.
File.WriteAllText(logFilePath, text);
// Ogni iterazione del ciclo legge e gestisce SOLO UNA riga
foreach (string line in File.ReadLines(logFilePath))
Console.WriteLine($"- {line}");
Vantaggio: Il file viene letto e gestito a pezzi, così consumi molta meno memoria quando lavori con file grandi.
Differenza tra File.ReadLines() e File.ReadAllLines():
- File.ReadAllLines(path): Carica tutte le righe del file subito in RAM e le restituisce come array string[]. Dopo la chiamata, tutto il file è già in memoria. Comodo se ti servono tutte le righe insieme (tipo per ordinare o cercare).
- File.ReadLines(path): Restituisce IEnumerable<string>, che ti permette di scorrere le righe una alla volta. Il file viene letto solo quando chiedi la riga successiva (tipo in un ciclo foreach). È più efficiente per file molto grandi, quando non puoi o non vuoi caricare tutto in memoria.
4. Scrivere un array di stringhe
A volte hai una lista di stringhe (magari da una collezione) e vuoi scrivere ogni stringa come una riga separata su file.
File.WriteAllLines(path, contents)
Questo metodo prende un array di stringhe (o qualsiasi collezione di stringhe) e scrive ogni stringa come una riga separata nel file. Come WriteAllText(), sovrascrive il file se già esiste.
string membersFilePath = "participants.txt";
string[] names = { "Ivan Petrov", "Maria Sidorova", "Sergey Kozlov", "Anna Kuznetsova" };
// Ogni riga dell'array sarà scritta come riga separata nel file
File.WriteAllLines(membersFilePath, names);
// Vediamo il contenuto
Console.WriteLine($"Contenuto di '{membersFilePath}':");
foreach (string line in File.ReadLines(membersFilePath))
Console.WriteLine($"- {line}");
5. Lavorare con file binari
La classe File non si limita solo ai dati testuali. Offre anche metodi comodi per leggere e scrivere dati binari, cioè byte grezzi. Utile, ad esempio, per salvare impostazioni in formato specifico, lavorare con immagini, audio o qualsiasi dato che non sia testo normale.
Scrivere un array di byte: File.WriteAllBytes() Crea o sovrascrive un file, scrivendo tutto l'array di byte fornito.
string binaryDataPath = "settings.bin";
// Esempio di dati binari: immagine, video, qualsiasi dato
byte[] settingsData = { 0x01, 0x0A, 0x2C, 0xFF, 0x00, 0x3F }; // Alcune "impostazioni grezze"
File.WriteAllBytes(binaryDataPath, settingsData);
Console.WriteLine($"Dati binari scritti nel file '{binaryDataPath}'.");
Importante: se provi ad aprire 'settings.bin' con il Blocco Note, vedrai caratteri illeggibili ("scarabocchi"), perché questi byte non sono pensati per essere interpretati come testo.
Leggere un array di byte: File.ReadAllBytes() Legge tutto il contenuto di un file binario in un array di byte.
if (File.Exists(binaryDataPath))
{
byte[] readSettings = File.ReadAllBytes(binaryDataPath);
Console.WriteLine($"Letti {readSettings.Length} byte dal file '{binaryDataPath}'.");
Console.Write("Byte letti (in esadecimale): ");
foreach (byte b in readSettings)
Console.Write($"{b:X2} "); // :X2 formatta il byte come numero esadecimale a due cifre
Console.WriteLine();
}
else
{
Console.WriteLine($"Errore: File binario '{binaryDataPath}' non trovato.");
}
Questi metodi sono molto comodi per salvare piccole porzioni di dati binari, tipo per cache di immagini piccole o oggetti serializzati.
6. Gestire i file sul disco
La classe File offre anche metodi per gestire i file sul disco.
Verificare se un file esiste: File.Exists(path) Restituisce true se il file nel percorso specificato esiste, e false altrimenti. È super consigliato usare questo metodo prima di provare a leggere, eliminare o fare altre operazioni che potrebbero causare errori se il file non c'è.
string myFileToCheck = "report.txt";
// Prima creiamolo per esempio
File.WriteAllText(myFileToCheck, "Report di prova.");
if (File.Exists(myFileToCheck))
{
Console.WriteLine($"File '{myFileToCheck}' trovato.");
// Ora puoi leggerlo, eliminarlo, ecc.
}
else
{
Console.WriteLine($"File '{myFileToCheck}' non trovato.");
}
Eliminare un file: File.Delete(path) elimina definitivamente il file dal disco. Fai molta attenzione, questa azione è irreversibile!
string fileToDelete = "temp_file.txt";
// Creiamo il file per esempio
File.WriteAllText(fileToDelete, "Questo file verrà eliminato.");
if (File.Exists(fileToDelete))
{
File.Delete(fileToDelete);
Console.WriteLine($"File '{fileToDelete}' eliminato con successo.");
}
else
{
Console.WriteLine($"File '{fileToDelete}' non esiste più o era già stato eliminato.");
}
7. Eccezioni quando lavori con i file
Lavorare con i file vuol dire sempre interagire con il sistema operativo e risorse esterne, quindi è sempre possibile che succedano errori.
Gestisci le eccezioni (errori)! Quando lavori con i file possono succedere un sacco di problemi che il tuo programma deve saper gestire bene, non solo "schiantarsi". Situazioni tipiche:
- FileNotFoundException: Il file a cui vuoi accedere non esiste nel percorso specificato.
- DirectoryNotFoundException: La cartella dove vuoi creare o trovare il file non esiste.
- UnauthorizedAccessException: Il tuo programma non ha i permessi per leggere, scrivere o eliminare il file/cartella (tipo file di sistema o in area protetta).
- IOException: Errore generico di input/output, spesso capita se il file è già aperto da un altro programma e bloccato, o se il disco è pieno.
Usa sempre la struttura try-catch per lavorare in sicurezza con i file. Così puoi "catturare" l'errore e fare qualcosa (tipo mostrare un messaggio all'utente, scrivere su un log).
string sensitiveFilePath = "secret_data.txt";
try
{
// Prova a leggere un file che magari non esiste o a cui non hai accesso
string secretContent = File.ReadAllText(sensitiveFilePath);
Console.WriteLine($"Contenuto segreto letto: {secretContent}");
}
catch (FileNotFoundException) // Errore specifico: file non trovato
{
Console.WriteLine($"Errore: File '{sensitiveFilePath}' non trovato. Controlla che il percorso sia giusto.");
}
catch (IOException ex) // Errore generico di input/output (tipo file bloccato)
{
Console.WriteLine($"Errore di input/output con il file '{sensitiveFilePath}': {ex.Message}");
}
catch (Exception ex) // Qualsiasi altro errore inaspettato
{
Console.WriteLine($"Errore imprevisto: {ex.Message}");
}
8. Quando System.IO.File non è la scelta migliore?
Anche se la classe File è super versatile, ci sono casi in cui non è ottimale o addirittura non puoi usarla:
File enormi (decine+ di gigabyte): I metodi ReadAllText()/ReadAllBytes() caricano tutto il file in memoria. Per file enormi questo porta a OutOfMemoryException. In questi casi File.ReadLines() (per testo) o l'approccio diretto con stream (FileStream) è necessario per leggere/scrivere a pezzi. (Degli stream parleremo nella prossima lezione).
Controllo fine sull'accesso ai file: Se ti serve un controllo molto specifico su come apri il file (tipo accesso esclusivo, così nessun altro può leggerlo o scriverci, o al contrario accesso in lettura da più processi), File non ti dà questo livello di dettaglio. Per questo servono classi di stream più low-level.
Operazioni asincrone: Per app ad alte prestazioni che devono fare operazioni sui file senza bloccare il thread principale (tipo nei web server), servono metodi asincroni (tipo ReadAllTextAsync()), che si basano comunque sugli stream.
Lettura/scrittura parziale: Se ti serve leggere solo una parte del file da un certo offset o scrivere dati in mezzo a un file esistente, i metodi di File non vanno bene. Serve accesso diretto allo stream del file.
9. Riepilogo
Perché System.IO.File è così popolare?
- Semplicità e sintesi: I suoi metodi statici ti permettono di fare operazioni complesse in una o due righe di codice. Questo semplifica molto lo sviluppo e rende il codice più leggibile.
- Gestione automatica delle risorse: File si occupa di aprire e chiudere i file per ogni operazione, così non devi gestire le risorse a mano (che spesso porta a errori).
- Versatilità per i task quotidiani: Per la maggior parte delle operazioni tipiche con file di testo e binari di piccole/medie dimensioni, File basta e avanza.
- Diffusione: Nella maggior parte dei task pratici e ai colloqui si aspettano che tu sappia usare bene i metodi della classe File.
- Complessità nascosta: Tutti i metodi sono scritti per incapsulare la logica complessa dell'interazione col sistema operativo, dandoti un'interfaccia pulita e chiara.
Domande frequenti e chiarimenti
Qual è la dimensione massima di file accettata da File.ReadAllText()?
Per file fino a qualche decina di megabyte (< 100 MB) di solito non ci sono problemi. Se il file è più grande (centinaia di MB, gigabyte), usare File.ReadAllText() può portare a esaurire la RAM (eccezione OutOfMemoryException). In questi casi meglio usare File.ReadLines() per leggere riga per riga o strumenti più avanzati per l'elaborazione in streaming, di cui parleremo più avanti.
Si può creare/scrivere un file in una cartella che non esiste?
No. I metodi File.WriteAllText(), File.WriteAllBytes() e altri non creano le cartelle genitrici in automatico. Se specifichi un percorso tipo C:\MyAppData\Logs\current.log, ma le cartelle MyAppData o Logs non esistono, avrai una DirectoryNotFoundException. Prima devi assicurarti che tutte le cartelle esistano, usando i metodi della classe System.IO.Directory (tipo Directory.CreateDirectory("myfolder")).
Come faccio a sapere la data di creazione, modifica o la dimensione di un file?
Per avere info più dettagliate su un file, tipo data di creazione, ultima modifica, dimensione, attributi (nascosto, sola lettura ecc.), usa la classe System.IO.FileInfo. Ti dà un sacco di proprietà utili.
GO TO FULL VERSION