CodeGym /Corsi /C# SELF /Immutabilità e with-e...

Immutabilità e with-espressioni

C# SELF
Livello 19 , Lezione 2
Disponibile

1. Cos'è l'immutabilità?

Partiamo da un'analogia: immagina di essere un contabile che ogni giorno prepara un report sulle vendite. Da vero professionista, non modifichi il report della settimana scorsa, ne crei uno nuovo — basato su quello vecchio, ma con dati aggiornati. Lo stesso vale per gli oggetti immutabili in programmazione: una volta creato, quell'oggetto non cambia più, e ogni "modifica" significa creare una nuova copia.

Immutabilità (immutability) — è la proprietà di un oggetto di non cambiare dopo l'inizializzazione. Tutte le sue proprietà diventano "congelate": se vuoi un valore diverso — crea un nuovo oggetto.

Perché serve?

  • Garantisce sicurezza: se nessuno può cambiare il tuo oggetto per sbaglio, i tuoi dati non verranno rovinati all'improvviso. Questo succede spesso nei programmi multithread, quando più thread cercano di cambiare qualcosa contemporaneamente.
  • Debug più facile: se l'oggetto non cambia, sai esattamente cosa è successo dopo la sua creazione.
  • Comodo per passare dati, soprattutto nei sistemi distribuiti, dove le copie possono divergere.
  • Permette di fare "istantanee dello stato" (snapshots) — la storia delle modifiche diventa evidente.

2. Immutabilità nei record-tipi

Quando dichiari una normale class, le sue proprietà sono mutabili (mutable) di default. Esempio:

public class UserProfile
{
    public string Nome { get; set; }
    public int Eta { get; set; }
}

var user = new UserProfile { Nome = "Ivan", Eta = 25 };
user.Eta = 26; // Tutto ok — classe normale: cambiamo l'età "al volo"

Con record la situazione è diversa: sono pensati per contenere dati immutabili.


public record UserProfile(string Nome, int Eta);

// Creiamo l'oggetto:
var user = new UserProfile("Ivan", 25);

// Proviamo a cambiare l'età:
user.Eta = 26; // Errore di compilazione: proprietà di sola lettura!
record — proprietà di sola lettura (init-only)

Le proprietà di un record posizionale sono di sola lettura (init-only). Non puoi cambiarle dopo la creazione, ma puoi usare una with-espressione per creare una nuova copia con una proprietà modificata.

Classi mutabili vs record immutabili

Classe Record posizionale
Proprietà di default
get; set;
Immutabili
get; init;
Come cambiare
Accesso diretto alla proprietà
Solo creando una nuova copia
Confronto oggetti
Per riferimento (ReferenceEquals)
Per valore (Equals)
Comodo per passare dati
Non sempre

3. with-espressioni

Probabilmente ti sei chiesto — "record sono fighi, ma come si vive se non si possono cambiare?" Ecco dove arriva la magia delle with-espressioni!

with — è una sintassi speciale che ti permette di creare una nuova copia di un record, cambiando solo le proprietà che vuoi.
Cioè: "Prendi questo oggetto, fanne una copia, ma qui cambia un paio di proprietà."

Esempio base


var user1 = new UserProfile("Anna", 30);
// ... ma la vita va avanti, e Anna è diventata più grande
var user2 = user1 with { Eta = 31 };
// user1 è rimasta la stessa, user2 — copia, ma con un anno in più

Console.WriteLine(user1); // UserProfile { Nome = Anna, Eta = 30 }
Console.WriteLine(user2); // UserProfile { Nome = Anna, Eta = 31 }

Sotto il cofano

Non è un clone mutante, ma un nuovo oggetto, creato tramite un metodo speciale autogenerato Clone(), che crea una copia e inserisce i nuovi valori.

Se le with-espressioni esistessero nella vita reale, potresti svegliarti la mattina non nel "vecchio corpo stanco", ma in una copia di te stesso con l'umore giusto e muscoli più grandi (ma solo se fossi un record).

4. Un po' su annidamento e copia

Se un record contiene altri record — tutto ok:

public record Indirizzo(string Citta, string Via);
public record Studente(string Nome, int Eta, string Email, Indirizzo Casa);

var a1 = new Indirizzo("Mosca", "Tverskaya");
var s1 = new Studente("Lena", 21, "lena@mail.ru", a1);

var s2 = s1 with { Casa = a1 with { Via = "Arbat" } };

Qui tutto funzionerà davvero in modo immutabile, perché il Indirizzo annidato è anch'esso un record.

5. Ultime sfumature

Record posizionali = compattezza

Puoi dichiarare un record in forma "corta" (sintassi posizionale). In questo caso tutte le proprietà sono automaticamente init-only.

public record Corso(string Nome, int Crediti);

var c1 = new Corso("C#", 5);
var c2 = c1 with { Crediti = 6 };

Analogia con proprietà di sola lettura (init-only)

In un record puoi dichiarare esplicitamente le proprietà così:

public record Studente
{
    public string Nome { get; init; }
    public int Eta { get; init; }
}

Anche queste proprietà possono essere cambiate solo all'inizializzazione (o tramite with).

6. Pratica: demo-app

Scriviamo la nostra "Scuola Online" didattica. Supponiamo di avere già un record per lo studente:

public record Studente(string Nome, int Eta, string Email);

Classico: qualcuno ha sbagliato l'indirizzo, ma lo studente ha già creato l'account. Come fare "l'aggiornamento" dell'email? Ovviamente con with!


var studente = new Studente("Ekaterina", 19, "kate@school.com");
var studenteAggiornato = studente with { Email = "ekaterina@school.com" };

// Controlliamo gli oggetti:
Console.WriteLine(studente);       // Studente { Nome = Ekaterina, Eta = 19, Email = kate@school.com }
Console.WriteLine(studenteAggiornato); // Studente { Nome = Ekaterina, Eta = 19, Email = ekaterina@school.com }

7. Errori tipici e trappole

Ora un po' di dolore — dove gli studenti sbagliano più spesso giocando con i record immutabili.

  • Primo, molti pensano che with cambi l'oggetto originale. In realtà, l'oggetto originale resta lo stesso, e quello nuovo viene creato con i campi modificati. A volte per questo si può cadere nella trappola e perdere i nuovi valori.
  • Secondo, ricorda: se dentro un record ci sono oggetti annidati mutabili (ad esempio un array o una List), la with-espressione non fa una copia profonda! La tua collezione sarà la stessa per entrambe le copie.

public record Studente(string Nome, int Eta, List<string> Materie);

var s1 = new Studente("Oleg", 22, new List<string> { "Math", "Physics" });
var s2 = s1 with { };

s1.Materie.Add("C#"); // Ops, ora anche s2.Materie include "C#"

Ecco perché per uno stato truly immutable è meglio usare solo tipi semplici o collezioni che sono già immutabili (ImmutableList<T> e altre da System.Collections.Immutable).

Se vuoi garantire una vera immutabilità, usa queste collezioni o fai una copia profonda manualmente.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION