📢 Nuovo Corso Bootstrap Completo disponibile!

Pattern Matching Avanzato in C#

Il pattern matching in C# è una potente funzionalità che consente di confrontare valori con modelli specifici, migliorando la leggibilità e la concisione del codice. Introdotto inizialmente con C# 7.0 e successivamente ampliato, il pattern matching ha trasformato il modo in cui gli sviluppatori possono gestire il controllo del flusso basato su condizioni complesse. In questa guida esploreremo le tecniche avanzate di pattern matching in C#, i loro usi più efficaci e le best practices per integrarli nel tuo codice.

Cos’è il Pattern Matching?

Il pattern matching permette di confrontare un valore con un modello specifico e, se corrisponde, eseguire un’azione. Questo approccio va oltre le tradizionali dichiarazioni if o switch, permettendo confronti più sofisticati e operazioni condizionali con una sintassi più pulita.

Sintassi di Base

La sintassi di base del pattern matching include l’uso di parole chiave come is, switch, e when per confrontare un valore con un modello specifico.

Esempio di Pattern Matching con is

object obj = "Ciao";
if (obj is string s)
{
Console.WriteLine($"Stringa: {s}");
}

In questo esempio, obj viene confrontato con il tipo string, e se la corrispondenza ha esito positivo, viene estratto il valore come variabile s.

Tipi di Pattern Matching

1. Pattern Matching con switch

Il pattern matching con switch è stato ampliato in C# 8.0 e successivi, permettendo un controllo del flusso più flessibile e leggibile rispetto al switch tradizionale.

Esempio di Switch Expression

public static string ClassificaOggetto(object obj) => obj switch
{
int i when i > 0 => "Numero positivo",
int i when i < 0 => "Numero negativo",
int => "Zero",
string s => $"Stringa: {s}",
null => "Nessun valore",
_ => "Tipo sconosciuto"
};

In questo esempio, switch viene utilizzato come espressione per restituire un risultato in base al tipo e al valore di obj.

2. Property Pattern

Il property pattern permette di confrontare oggetti basati su valori specifici delle loro proprietà.

Esempio di Property Pattern

public class Persona
{
public string Nome { get; set; }
public int Età { get; set; }
}
public static string DescriviPersona(Persona persona) => persona switch
{
{ Nome: "Mario", Età: 30 } => "Mario, 30 anni",
{ Età: var età } when età < 18 => "Minorenne",
{ Età: var età } => $"Età: {età}",
_ => "Persona sconosciuta"
};

In questo esempio, il switch utilizza un property pattern per controllare i valori delle proprietà Nome ed Età di persona.

3. Tuple Pattern

Il tuple pattern permette di confrontare combinazioni di valori con modelli di tuple.

Esempio di Tuple Pattern

public static string ClassificaPunto(int x, int y) => (x, y) switch
{
(0, 0) => "Origine",
( > 0, > 0) => "Quadrante 1",
( < 0, > 0) => "Quadrante 2",
( < 0, < 0) => "Quadrante 3",
( > 0, < 0) => "Quadrante 4",
_ => "Sull'asse"
};

In questo esempio, il switch esamina una tupla (x, y) e restituisce un valore basato sulla posizione del punto in un piano cartesiano.

4. Positional Pattern

Il positional pattern è usato per deconstruttori, permettendo il pattern matching su singoli elementi di una struttura complessa.

Esempio di Positional Pattern

public class Punto
{
public int X { get; }
public int Y { get; }
public Punto(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
public static string ClassificaPunto(Punto punto) => punto switch
{
(0, 0) => "Origine",
( > 0, > 0) => "Quadrante 1",
( < 0, > 0) => "Quadrante 2",
( < 0, < 0) => "Quadrante 3",
( > 0, < 0) => "Quadrante 4",
_ => "Sull'asse"
};

In questo esempio, Punto è una classe che può essere deconstrutta, e switch utilizza il positional pattern per classificare il punto in base alla sua posizione.

5. Relational Pattern

I relational pattern consentono di utilizzare operatori di confronto all’interno di pattern, offrendo un controllo più fine sui confronti.

Esempio di Relational Pattern

public static string ClassificaNumero(int numero) => numero switch
{
< 0 => "Negativo",
0 => "Zero",
> 0 => "Positivo"
};

In questo esempio, il switch utilizza operatori di confronto per classificare numero come negativo, zero o positivo.

6. Logical Pattern

I logical pattern combinano diversi pattern usando operatori logici come and, or, e not.

Esempio di Logical Pattern

public static string ClassificaTemperatura(int gradiCelsius) => gradiCelsius switch
{
< 0 => "Ghiaccio",
>= 0 and < 20 => "Freddo",
>= 20 and <= 30 => "Confortevole",
> 30 => "Caldo"
};

In questo esempio, and viene utilizzato per combinare i pattern in modo da classificare la temperatura in diverse categorie.

Best Practices per il Pattern Matching Avanzato

1. Semplifica il Codice

Usa il pattern matching per sostituire dichiarazioni if e switch verbose, semplificando il codice e rendendolo più leggibile.

2. Usa _ per Gestire Casi Predefiniti

L’uso di _ come pattern di default garantisce che tutti i casi siano gestiti, prevenendo potenziali errori.

3. Combina Pattern

Combina diversi tipi di pattern (property, tuple, relational, ecc.) per gestire scenari complessi in modo conciso.

4. Documenta le Logiche Complesse

Quando utilizzi pattern matching avanzato, assicurati di documentare chiaramente la logica, specialmente se usi combinazioni di pattern complessi.

5. Sfrutta i Deconstructor

Implementa deconstructor nelle tue classi per sfruttare al massimo il positional pattern, migliorando la leggibilità e la manutenibilità del codice.

Conclusione

Il pattern matching avanzato in C# è uno strumento potente che consente di scrivere codice più chiaro, conciso e mantenibile. Sfruttando le capacità offerte dai vari pattern, come il property pattern, tuple pattern, relational pattern e logical pattern, puoi gestire condizioni complesse in modo elegante e leggibile. Seguendo le best practices, puoi integrare il pattern matching avanzato nelle tue applicazioni, migliorando la qualità e l’efficienza del tuo codice.