Sappiamo tutti che appena il numero delle righe delle nostre applicazioni comincia ad aumentare c'è anche la necessità di scaricare dati e/o configurazioni sul disco fisso.
Come creare quindi dei file su disco?
Ci vengono in aiuto diverse classi del Framework, ognuna ottimizzata in base al tipo di dati che scriveremo, contraddistinte da una parola finale che può essere
Reader relativa alle classi che permettono la lettura dei file o
Writer quelle che invece permettono la scrittura.
TextReader / TextWriter
(per la lettura di una sequenza di Byte, di solito si usa per file di tipo testo)
BinaryReader / BinaryWriter
(è una lettura di tipo Raw, leggi direttamente i Byte e li inserisce in array di byte, si usa solitamente per caricare per esempio immagini, video, audio, ecc.)
StreamReader / StreamWriter
(è una classe derivata dalla TextReader/Writer e permette di leggere stream (sequenze) di byte con una codifica specifica (es. ANSI, Unicode, UTF8, ecc.)
StringReader / StringWriter
(anch'essa derivata dalla TextReader/Writer permette la scrittura di byte all'interno di stringhe facendo uso della classe StringBuilder ottimizzata quando bisogna fare operazioni di inserimento, eliminazione, ecc.)
Come indicato dal titolo andremo ad esaminare le prime due classi dell'elenco qui sopra: TextReader e TextWriter.
Come creare un file?
E' un'operazione semplicissima ci servirà per gli esempi successivi, ma prima di cominciare, ricordiamo che queste classi sono contenute nel namespace System.IO, quindi per poterle utilizzare sarà necessario richiamare il namespace come solito:
using System.IO;
Queste due righe di codice non fanno altro che creare un file vuoto su C: chiamato output.txt e chiudere il file rilasciando gli handle (il file è così libero, e può essere utilizzato da altri processi, applicazioni, in caso contrario avrebbe avuto un lock e ogni operazione veniva bloccata).
FileStream fs=File.Create("C:\\output.txt");
fs.Close();
Con TextWriter aggiungiamo del testo al nostro file.
Il file di testo è stato creato ora utilizziamo la classe TextWriter per scrivere:
TextWriter tw=File.AppendText("C:\\output.txt");
tw.WriteLine("1° riga");
tw.WriteLine("2° riga");
tw.WriteLine("3° riga");
tw.WriteLine("4° riga");
tw.Close();
Come potete notare nella prima riga il file viene aperto in modalità Append ossia tutto le righe di testo che verranno aggiunte finiranno in coda al file.
Dalla seconda alla quinta riga utilizziamo il metodo WriteLine che scrive fisicamente i byte di testo sul file e inserisce un CRLF (\r\n) al termine della riga.
Per chi volesse gestire manualmente i ritorni a capo è possibile usare il semplice metodo Write.
Questi due metodi hanno molti overloads ed è possibile dare come parametri qualsiasi tipo di dato base (come int, float, decimal, object, array di char, stringhe con parametri, ecc.)
Come ultima riga c'è la chiusura del TextWriter. E' molto importante non lasciare aperti, ciò significherebbe lasciare Handle a livello di OS aperti sul file e potrebbe causare comportamenti indesiderati nell'applicazione oppure blocchi con eccezioni.
Leggiamo il file con TextReader
Ci prepariamo ora ad affrontare il percorso contrario e cioè leggere il file.
Il codice necessario per questa operazioni è il seguente:
string riga="";
TextReader tr=File.OpenText("C:\\output.txt");
do
{
riga=tr.ReadLine();
Console.WriteLine(riga);
} while (riga!=null);
tr.Close();
Anche in questo caso le righe di codice necessarie sono veramente poche.
Abbiamo creato una stringa (riga) di supporto e inizializzata.
Viene aperto in lettura in modalità testo (File.OpenText) UTF-8 il file output.txt.
Poi c'è un ciclo do while che legge una ad una le stringhe e le scrive a Console fino a quando il puntatore al file non giunge o supera la fine.
In ultima riga, la chiusura del TextWriter come fatto in precedenza.
Metodi per la lettura con TextReader
E' bene sottolineare che per leggere un file sono disponibili diversi metodi e diverse modalità.
Le elenco qui di seguito:
- Read (legge un carattere e avanza il puntatore al carattere successivo)
- Readline (legge una riga intera fino a quando incontra un CRLF)
- ReadBlock (legge uno specifico numero di caratteri e li scrive in un buffer)
- ReadToEnd (legge l'intero file e lo riversa in una stringa)
Usiamo contemporaneamente TextReader e TextWriter!
Per terminare l'articolo volevo solo proporvi un esempio molto utile per comprendere totalmente il funzionamento di queste classi e le possibilità che offrono e unificare i tre esempi utilizzati precedentemente.
Con questo esempio si crea esattamente una copia identica di un file. E' più o meno il funzionamento della funzione copy dell'MS-DOS.
//Crea un secondo file output2.txt
FileStream fs=File.Create("C:\\output2.txt");
fs.Close();
//Inizializzazione e apertura dei due file
TextReader tr=File.OpenText("C:\\output.txt");
TextWriter tw=File.AppendText("C:\\output2.txt");
string riga=""; //Stringa di supporto, funzionerà da buffer
do
{
riga=tr.ReadLine(); //Legge la riga dal file output.txt
tw.WriteLine(riga); //Scrive la riga appena letta nel file output2.txt
} while (riga!=null);
//Chiusura dei due file, liberiamo gli handles
tr.Close();
tw.Close();
Il codice di questo esempio è disponibile per lo scaricamento sia in linguaggio C# che in VB.NET
N.B. E' possibile che il file output2.txt sia più grande del primo di due byte. Cioè deriva dal fatto che è presente un CRLF in più visto che durante la scrittura del file bisognerebbe controllare che il contenuto della stringa riga non sia null, oppure impostare il ciclo diversamente.
Ho scelto questo modo per lasciare il codice più pulito è utilizzare il minimo indispensabile.