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.