1. Introduzione
Siamo già abituati a creare oggetti nei nostri programmi. Ricordate, stiamo per iniziare a studiare classi e oggetti più a fondo, ma già ora capiamo che variabili, liste, anche semplici stringhe — non sono roba casuale, sono delle "entità" nel nostro programma. Per esempio, possiamo creare una variabile int age = 30; o string name = "Vasia";. Ma cosa succede se dobbiamo salvare su file le informazioni su un intero utente che ha nome, età, indirizzo, una lista di libri preferiti e molto altro?
Immagina: stai scrivendo un gioco. Hai un oggetto Player con tante caratteristiche: salute, livello, inventario (lista di item), coordinate sulla mappa e così via. Il giocatore gioca, migliora, trova artefatti fighi. E poi decide di uscire dal gioco. Cosa succede? Tutti i dati sulle sue avventure che erano in memoria spariscono! Che tristezza! Per evitare questo dobbiamo salvare lo stato dell'oggetto Player su file, e quando il giocatore torna, caricarlo di nuovo.
Ed è qui che entra in scena la serializzazione. Si occupa proprio di costruire un ponte tra gli oggetti "vivi" in memoria e i dati "morti", ma persistenti, su disco.
2. Processo di serializzazione (da oggetto a file)
Analizziamo come avviene questa "magia" che trasforma un oggetto in byte che possono essere scritti su file.
Immaginate che abbiamo questa classe Book (Libro):
// Questo è il nostro "disegno" o "piano" per creare oggetti libro
public class Book
{
// Proprietà del libro
public string Title { get; set; } // Titolo del libro
public string Author { get; set; } // Autore
public int Year { get; set; } // Anno di pubblicazione
// Costruttore - metodo speciale per creare nuovi oggetti Book
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
// Metodo per stampare comodamente le informazioni sul libro (non obbligatorio per la serializzazione, ma utile)
public void DisplayInfo()
{
Console.WriteLine($"Titolo: {Title}, Autore: {Author}, Anno: {Year}");
}
}
Passo 1: Creare l'oggetto da serializzare.
Prima di tutto ci serve l'oggetto che vogliamo salvare. Per esempio abbiamo creato un'istanza di Book:
Book myFavoriteBook = new Book("Avtostopom po Galaktike", "Duglas Adams", 1979);
Questo oggetto myFavoriteBook è adesso nella memoria RAM.
Passo 2: Scelta dello strumento (serializer).
Non possiamo semplicemente "copiare" l'oggetto su disco. Il computer non capisce direttamente gli oggetti nei file — ha bisogno di byte. Serve uno strumento speciale — il serializer. Il suo compito è scomporre il nostro oggetto nelle sue parti (le sue proprietà: Title, Author, Year) e trasformare queste parti in una sequenza di byte o in una stringa di testo (per esempio JSON o XML).
Oggi non entriamo nelle implementazioni specifiche — pensiamolo solo come una "scatola-convertitore".
Passo 3: Trasformare l'oggetto in uno stream di dati.
Il serializer prende il nostro oggetto myFavoriteBook, guarda le sue proprietà (Title, Author, Year) e converte ciascuna in un formato che si può scrivere. Tutti questi byte (o caratteri di testo) vengono raccolti in un unico flusso di dati — una lunga "striscia" di informazioni.
Passo 4: Scrivere lo stream su file.
Ora che abbiamo questa "striscia di byte", usiamo i nostri vecchi amici FileStream e, se serve, StreamWriter (se scegliamo un formato testuale come JSON) o solo FileStream (per dati puramente binari), per scrivere questo flusso su disco.
3. Processo di deserializzazione (da file a oggetto)
Passo 1: Lettura dello stream di dati dal file.
Usiamo di nuovo FileStream e se necessario StreamReader (se è un formato testuale) per leggere il contenuto del file. I dati arrivano come una "striscia" di byte o testo.
Passo 2: Scelta dello strumento (deserializer).
Serve lo strumento inverso — il deserializer. Deve sapere come interpretare i byte/testo ricevuti e ricostruire correttamente la struttura dell'oggetto. È molto importante: per deserializzare si usa lo stesso tipo di serializer (e di solito la stessa libreria) che si è usata per serializzare, altrimenti il "costruttore" non capirà le istruzioni di ricomposizione.
Passo 3: Trasformare lo stream di nuovo in oggetto.
Il deserializer legge i dati, capisce dove sta Title, poi Author, poi Year, e sulla base di questo crea un nuovo oggetto Book in memoria, popolando le sue proprietà.
Passo 4: Ottenere l'oggetto pronto.
Voilà! Abbiamo di nuovo un oggetto Book completo in memoria, con cui possiamo lavorare.
Esempio: Salviamo il nostro "Super-Libro" manualmente (per capire il concetto)
Per ora non useremo librerie specializzate: facciamo la serializzazione e deserializzazione più semplice "manuale" con StreamWriter e StreamReader. Questo aiuta a capire il principio.
Il nostro oggetto Book:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
public void DisplayInfo()
{
Console.WriteLine($"Titolo: \"{Title}\", Autore: {Author}, Anno: {Year}");
}
}
Serializzazione manuale: metodo SaveBookToTextFile
Creiamo un metodo che salvi le proprietà del libro in un file di testo, una proprietà per riga.
void SaveBookToTextFile(Book book, string filePath)
{
using var writer = new StreamWriter(filePath);
writer.WriteLine(book.Title);
writer.WriteLine(book.Author);
writer.WriteLine(book.Year);
}
Cosa succede? Apriamo StreamWriter e scriviamo in sequenza Title, Author, Year — questo è il nostro schema di serializzazione più semplice.
Se esegui il codice, il contenuto del file sarà così:
Avtostopom po Galaktike
Duglas Adams
1979
Deserializzazione manuale: metodo LoadBookFromTextFile
Scriviamo un metodo che legga i dati e costruisca un nuovo oggetto Book.
Book LoadBookFromTextFile(string filePath)
{
using var reader = new StreamReader(filePath);
string title = reader.ReadLine();
string author = reader.ReadLine();
int year = int.Parse(reader.ReadLine());
return new Book(title, author, year);
}
E usiamo questi metodi in Main:
//creiamo l'oggetto
var myBook = new Book("Avtostopom po Galaktike", "Duglas Adams", 1979);
string filePath = "my_favorite_book.txt";
//lo salviamo su file
SaveBookToTextFile(myBook, filePath);
//leggiamo dal file
Book loadedBook = LoadBookFromTextFile(filePath);
Cosa succede in LoadBookFromTextFile? Apriamo StreamReader e leggiamo nello stesso ordine le righe: prima il titolo, poi l'autore, poi l'anno e lo convertiamo con int.Parse. Dopo creiamo una nuova istanza di Book.
In pratica è bene aggiungere controlli (File.Exists) e gestione degli errori con try-catch, ma qui ci concentriamo sull'idea di base.
Perché la serializzazione "manuale" è problematica (e perché servono le librerie)?
- Molto codice manuale. Ogni proprietà va scritta e poi letta. Se gli oggetti e i campi sono tanti, il codice cresce molto.
- Fragilità ai cambiamenti. Aggiunto un nuovo campo Pages (int)? Bisogna cambiare sia la scrittura che la lettura, e tenere strettamente l'ordine.
- Strutture complesse. Collezioni e oggetti annidati (per esempio List<Chapter>) trasformano il codice in "spaghetti".
- Formati ed efficienza. Il formato testuale è semplice, ma non sempre compatto o sicuro; per dati binari bisogna lavorare manualmente con i byte, BinaryWriter/BinaryReader ecc.
- Mancanza di metadati. Il nostro file non "sa" che la prima riga è Title e la terza è Year. I serializer specializzati possono salvare metadati e essere più resistenti ai cambiamenti del modello.
Proprio per questo nei progetti reali si usano librerie serializer pronte, che sanno automaticamente decomporre/ricomporre oggetti, lavorare con JSON, XML e formati binari e gestire meglio le evoluzioni dei modelli. Ne parleremo nella prossima lezione!
Applicazioni pratiche: a cosa serve la serializzazione?
- Salvataggio e caricamento dei dati. Impostazioni, stati di gioco, configurazioni.
- Trasmissione dei dati in rete. Scambio di oggetti complessi tra servizi (spesso in JSON).
- Caching. Riutilizzo rapido di dati già ottenuti.
- Logging di oggetti complessi. Utile per debug e audit.
- Copia profonda degli oggetti. Serializzazione + deserializzazione come modo per clonare un grafo di oggetti.
La serializzazione è uno dei pilastri del software moderno: dal salvataggio del progresso di un gioco al lavoro con web service — è ovunque!
GO TO FULL VERSION