Introduzione
Il pattern matching introdotto con C# 7 e ampliato nelle versioni successive (C# 8, 9, 10 e oltre) è ormai una caratteristica matura del linguaggio. Non si tratta più di un semplice strumento per controllare il tipo di un oggetto, ma di un costrutto espressivo che migliora leggibilità, sicurezza e manutenibilità del codice.
In questo articolo andremo oltre le basi, analizzando:
- le guard clauses integrate nei pattern,
- gli switch avanzati e le espressioni compatte,
- le relazioni nei pattern (>, <, >=, ecc.),
- esempi reali su come questi strumenti migliorano codice in scenari complessi.
Ripasso rapido: cos’è il Pattern Matching in C#
Tradizionalmente, pattern matching in C# viene associato a costrutti come:
if (obj is string s)
{
Console.WriteLine($"È una stringa di lunghezza {s.Length}");
}
Ma con le nuove versioni del linguaggio, possiamo andare ben oltre, arrivando a descrivere logiche complesse in modo dichiarativo ed espressivo.
Guard Clauses: condizioni raffinate nel pattern matching
Le guard clauses permettono di estendere un pattern con condizioni aggiuntive, evitando annidamenti inutili.
Esempio classico senza guard clause
if (order is OnlineOrder o)
{
if (o.Total > 100 && o.Customer.IsPremium)
{
Console.WriteLine("Applica sconto premium");
}
}
Con guard clause (C# 9+)
if (order is OnlineOrder { Total: > 100 } o && o.Customer.IsPremium)
{
Console.WriteLine("Applica sconto premium");
}
Qui il pattern è autoesplicativo: controlla il tipo, il valore di una proprietà e condizione extra sul cliente.
💡 Best practice: usare guard clauses per ridurre nesting e migliorare la leggibilità logica.
Switch Expression Avanzati: dal boilerplate alla chiarezza
Lo switch
tradizionale era verboso, con case
e break
. Le switch expressions introdotte in C# 8 offrono un approccio conciso e potente.
Prima (switch classico)
string GetStatus(Order order)
{
switch (order.Status)
{
case OrderStatus.Pending:
return "In attesa";
case OrderStatus.Completed:
return "Completato";
case OrderStatus.Cancelled:
return "Annullato";
default:
return "Sconosciuto";
}
}
Dopo (switch expression)
string GetStatus(Order order) => order.Status switch
{
OrderStatus.Pending => "In attesa",
OrderStatus.Completed => "Completato",
OrderStatus.Cancelled => "Annullato",
_ => "Sconosciuto"
};
Ma il vero valore arriva con pattern complessi:
string EvaluateOrder(Order order) => order switch
{
{ Total: > 500, Customer.IsPremium: true } => "Ordine premium di alto valore",
{ Total: > 500 } => "Ordine importante",
{ Status: OrderStatus.Cancelled } => "Ordine annullato",
_ => "Ordine standard"
};
💡 Best practice: usare switch expressions per centralizzare logiche di decisione complesse e ridurre il rischio di “if-else ladder”.
Relational Patterns: espressioni più naturali
Con i relational patterns introdotti in C# 9 possiamo esprimere condizioni numeriche direttamente dentro i pattern.
Esempio
string ClassifyTemperature(int temp) => temp switch
{
< 0 => "Sotto zero",
< 20 => "Freddo",
< 30 => "Mite",
< 40 => "Caldo",
_ => "Estremo"
};
Non servono più catene di if-else
: la logica è compatta, leggibile e sicura.
Pattern Matching in scenari reali
Vediamo alcuni casi concreti dove il pattern matching avanzato semplifica il codice:
1. Validazione input in un API
IResult ValidateUser(User user) => user switch
{
{ Age: < 18 } => Results.BadRequest("Utente minorenne non ammesso"),
{ Age: > 120 } => Results.BadRequest("Età non valida"),
{ Email: null or "" } => Results.BadRequest("Email obbligatoria"),
_ => Results.Ok("Utente valido")
};
2. Motore di regole per promozioni e-commerce
string GetPromotion(Order order) => order switch
{
{ Total: > 200, Customer.IsPremium: true } => "Sconto 20%",
{ Total: > 100 } => "Sconto 10%",
{ Items.Count: > 5 } => "Spedizione gratuita",
_ => "Nessuna promozione"
};
3. Parsing messaggi in un sistema distribuito
void ProcessMessage(Message msg)
{
switch (msg)
{
case { Type: "OrderCreated", Payload: Order o }:
HandleOrder(o);
break;
case { Type: "UserRegistered", Payload: User u }:
HandleUser(u);
break;
case null:
throw new ArgumentNullException(nameof(msg));
default:
LogUnknown(msg);
break;
}
}
Quando NON usare il Pattern Matching
Come ogni strumento, anche il pattern matching va dosato. Evita di usarlo quando:
- rende il codice meno leggibile rispetto a un semplice
if
, - porta a scrivere logiche troppo dense e difficili da testare,
- sostituisce in modo improprio il polimorfismo OOP, che in certi casi resta la scelta migliore.
Conclusione
Il pattern matching in C# è molto più che un controllo di tipo: è uno strumento potente per esprimere logiche di business in modo compatto, leggibile e sicuro.
- Le guard clauses aiutano a ridurre il nesting.
- Le switch expressions centralizzano decisioni complesse.
- I relational patterns rendono naturali condizioni numeriche e range.
👉 Usare questi costrutti in modo consapevole significa scrivere codice più espressivo e manutenibile, dimostrando la padronanza delle feature moderne di C#.
FAQ su Pattern Matching in C#
1. Che cos’è il pattern matching in C#?
È una funzionalità che permette di verificare il tipo e la forma di un oggetto in modo compatto ed espressivo. Con le versioni più recenti di C#, il pattern matching supporta guard clauses, relational patterns e switch expressions avanzate.
2. Quali sono i vantaggi del pattern matching rispetto agli if-else tradizionali?
Riduce il codice boilerplate, aumenta la leggibilità e centralizza la logica di decisione. Inoltre, rende più sicuro il codice perché forza il controllo di tutti i casi rilevanti.
3. Quando conviene usare guard clauses nel pattern matching?
Quando vuoi evitare annidamenti (if
dentro if
) e rendere chiara la logica di validazione. Le guard clauses si usano spesso in controlli di business rules o validazioni di input.
4. Cos’è una switch expression e in cosa differisce da uno switch classico?
Una switch expression è una forma compatta dello switch che restituisce direttamente un valore. Rispetto allo switch classico elimina la necessità di case
, break
e default
, migliorando chiarezza e concisione.
5. Cosa sono i relational patterns in C#?
Sono pattern che permettono di usare operatori relazionali (<
, >
, <=
, >=
) direttamente dentro i case dello switch o in espressioni con pattern matching. Perfetti per classificazioni numeriche o range.
6. Ci sono casi in cui NON usare il pattern matching?
Sì: quando rende il codice più difficile da leggere rispetto a un semplice if
, quando viene usato al posto del polimorfismo OOP in modo forzato, o quando condensa troppe regole complesse in un unico switch difficile da mantenere.
7. Quali versioni di C# supportano i pattern avanzati?
- C# 7: introduzione base del pattern matching (
is
con type pattern). - C# 8: switch expressions e property patterns.
- C# 9: relational patterns e guard clauses.
- C# 10+: miglioramenti sintattici e semantici.
Documentazione ufficiale Microsoft
- Pattern matching in C# – Microsoft Docs
- Switch expressions – Microsoft Docs
- Property patterns – Microsoft Docs
- Relational patterns – Microsoft Docs
- Type patterns – Microsoft Docs
Blog e articoli tecnici di qualità
- Exploring Pattern Matching in C# 9 – InfoQ
- Advanced Pattern Matching in C# – Code Maze
- A deep dive into C# pattern matching – Telerik Blog
- Guard clauses in C# – Ardalis Blog
Video e risorse di approfondimento
Hai un progetto da sviluppare o da sistemare? Contattami per una consulenza