L’evoluzione di C# negli ultimi anni ha portato con sé strumenti sempre più potenti per chi vuole progettare modelli robusti, sicuri e facilmente manutenibili. In particolare, con l’introduzione di record types (C# 9), delle proprietà init-only e delle nuove possibilità di definire immutabilità controllata, oggi possiamo strutturare il dominio applicativo con un livello di espressività e affidabilità senza precedenti.
In questo articolo analizziamo come e quando usare questi strumenti, con esempi concreti e considerazioni architetturali.
Record types: valore semantico e confronto strutturale
Tradizionalmente in C# la maggior parte dei modelli è stata costruita con classi. Le classi sono potenti, ma portano con sé due caratteristiche che non sempre sono desiderabili:
- Confronto per riferimento (due oggetti con gli stessi valori non sono considerati uguali).
- Mutabilità di default, che aumenta il rischio di bug in scenari concorrenti o distribuiti.
I record types risolvono entrambi i problemi:
- Il confronto (
Equals
,GetHashCode
) avviene per valore. - Sono pensati per essere immutabili di default, pur consentendo scenari più flessibili.
public record Customer(string Id, string Name, string Email);
Con questo approccio:
- Due
Customer
con lo stessoId
,Name
edEmail
saranno uguali. - L’immutabilità rende i record perfetti per DTO, messaggi in un event bus o entity del dominio quando non serve mutare lo stato.
Proprietà init-only: costruzione immutabile e fluida
Sempre da C# 9 è stata introdotta la possibilità di usare l’init accessor.
Questa feature permette di assegnare un valore a una proprietà solo in fase di inizializzazione, lasciandola immutabile per il resto del ciclo di vita dell’oggetto.
public class Product
{
public string Id { get; init; }
public string Name { get; init; }
public decimal Price { get; init; }
}
Esempio di utilizzo:
var product = new Product
{
Id = "P123",
Name = "Aspirina",
Price = 9.99m
};
// product.Price = 8.99m; // ❌ Errore: proprietà init-only
Questo approccio offre un ottimo compromesso:
- Sintassi simile agli oggetti mutabili (inizializzazione fluida).
- Immutabilità garantita a runtime.
Immutabilità controllata: quando serve flessibilità
Non sempre l’immutabilità totale è la soluzione. Ci sono casi in cui vogliamo permettere una certa forma di mutazione, ma in modo controllato.
Tecniche comuni:
- Copy with expression (records)
Con i record possiamo creare una copia immutabile di un oggetto con valori aggiornati:var original = new Customer("C001", "Mario Rossi", "mario@example.com"); var updated = original with { Email = "nuovo@example.com" };
original
rimane invariato, mentreupdated
è una nuova istanza. - Encapsulamento con metodi di dominio
Se serve mutare lo stato, possiamo incapsulare la logica in metodi che rispettano le regole del dominio:public record Order(string Id, decimal Total) { public Order ApplyDiscount(decimal percentage) => this with { Total = Total * (1 - percentage) }; }
- Uso mirato di set privati
In alcuni casi, proprietà conprivate set
e costruttori protetti permettono mutazioni solo attraverso logiche ben definite:public class StockItem { public string Sku { get; } public int Quantity { get; private set; } public StockItem(string sku, int initialQty) => (Sku, Quantity) = (sku, initialQty); public void Decrease(int amount) { if (amount > Quantity) throw new InvalidOperationException(); Quantity -= amount; } }
Best practice per modelli robusti
- Usa i record per rappresentare entità immutabili come DTO, messaggi o configuration objects.
- Applica init-only per i modelli che necessitano di costruzione fluida, ma devono restare stabili a runtime.
- Incoraggia l’immutabilità controllata per le entità di dominio, garantendo che ogni cambiamento passi attraverso regole esplicite.
- Evita set pubblici se non strettamente necessari: riducono l’affidabilità del modello.
- Combina i record con pattern come Value Objects (DDD) per massimizzare chiarezza e consistenza.
Conclusione
L’introduzione di record types, init-only e tecniche di immutabilità controllata ha cambiato radicalmente il modo di progettare i modelli in C#.
Se fino a ieri la mutabilità era lo standard, oggi possiamo scrivere codice più robusto, prevedibile e sicuro, riducendo il rischio di bug difficili da tracciare e migliorando la qualità architetturale delle nostre applicazioni.
La vera sfida non è tecnica, ma architetturale: scegliere consapevolmente il livello di immutabilità che serve al proprio dominio.
Domande frequenti
1. Cosa sono i record in C#?
I record sono tipi introdotti in C# 9 progettati per rappresentare dati immutabili, con confronto per valore e supporto nativo al copy with expression.
2. A cosa servono le proprietà init-only?
Le proprietà init-only consentono di inizializzare un oggetto con sintassi fluida, mantenendolo però immutabile dopo la creazione.
3. Qual è la differenza tra immutabilità totale e immutabilità controllata?
L’immutabilità totale non permette modifiche allo stato, mentre quella controllata consente cambiamenti solo attraverso regole esplicite (ad esempio metodi di dominio o copy with).
4. Quando conviene usare i record rispetto alle classi?
I record sono ideali per DTO, value objects e messaggi immutabili, mentre le classi restano preferibili per entità di dominio con stato complesso e cicli di vita più lunghi.
5. Quali sono i vantaggi di modelli immutabili in C#?
Maggiore robustezza, meno bug dovuti a mutazioni non previste, facilità nei test, sicurezza in scenari concorrenti e codice più leggibile.
Contattami per avere una consulenza sul tuo progetto .net / c#