records immutabili

Records, init-only e immutabilità controllata: come progettare modelli robusti in C#

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:

  1. Confronto per riferimento (due oggetti con gli stessi valori non sono considerati uguali).
  2. 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 stesso Id, Name ed Email 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:

  1. 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, mentre updated è una nuova istanza.
  2. 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) }; }
  3. Uso mirato di set privati
    In alcuni casi, proprietà con private 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

  1. Usa i record per rappresentare entità immutabili come DTO, messaggi o configuration objects.
  2. Applica init-only per i modelli che necessitano di costruzione fluida, ma devono restare stabili a runtime.
  3. Incoraggia l’immutabilità controllata per le entità di dominio, garantendo che ogni cambiamento passi attraverso regole esplicite.
  4. Evita set pubblici se non strettamente necessari: riducono l’affidabilità del modello.
  5. 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#


Pubblicato

in

da

Tag: