C# Pattern Matching avanzato: guard clauses, switch expression e best practice

C# Pattern Matching avanzato: guard clauses, switch expression e best practice

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

Blog e articoli tecnici di qualità

Video e risorse di approfondimento


Hai un progetto da sviluppare o da sistemare? Contattami per una consulenza


Pubblicato

in

da