Menu

Switch expression in C#: sintassi, pattern, discard e when

La switch expression di C# 8 trasforma un valore in un risultato con rami basati su pattern invece di case e break. Sintassi, il discard _, pattern di proprietà, tuple e relazionali, and/or/not, guardie when, avvisi di esaustività e SwitchExpressionException, con equivalenti eseguibili in C# 7.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Una switch expression associa un valore di input a un risultato. È stata aggiunta in C# 8 e sostituisce lo schema comune di un'istruzione switch in cui ogni case si limita a restituire o assegnare qualcosa:

// C# 8 and later
string StatusText(int code) => code switch
{
    200 => "OK",
    301 or 302 => "Redirect",   // 'or' needs C# 9
    404 => "Not Found",
    >= 500 => "Server Error",   // relational patterns need C# 9
    _ => "Unknown"
};

Leggila dall'alto verso il basso: il valore prima di switch viene confrontato con ogni ramo in ordine, e il primo ramo il cui pattern corrisponde fornisce il risultato. Non ci sono case, break né la parola chiave default; _ (il pattern discard) corrisponde a qualsiasi cosa e fa da ripiego.

In C# 7, la stessa associazione è un'istruzione switch dentro un piccolo metodo, con un return in ogni case:

Output:

200 OK
302 Redirect
404 Not Found
503 Server Error
418 Unknown

La forma a espressione è più breve soprattutto perché ogni ramo sta su una riga e il corpo del metodo sparisce. È anche un'espressione, quindi può stare ovunque possa stare un valore: un argomento, un segnaposto di interpolazione, un return, l'inizializzatore di un campo.

Regole di sintassi

var result = input switch
{
    pattern1 => value1,
    pattern2 when condition => value2,
    _ => fallback
};
  • Prima viene l'input, poi switch. È l'ordine inverso rispetto alla forma a istruzione.
  • Ogni ramo è pattern => expression. Il lato destro deve essere una singola espressione; per una logica più articolata, chiama un metodo.
  • I rami sono separati da virgole. È consentita una virgola finale dopo l'ultimo ramo.
  • L'insieme è un'espressione, quindi un'istruzione che la usa termina con ; dopo la graffa di chiusura.
  • Tutti i rami devono produrre valori di un tipo comune. var x = n switch { 0 => "zero", _ => 0 }; non compila (CS8506, nessun tipo migliore). Da C# 9 un tipo di destinazione dichiarato risolve il problema: object x = n switch { 0 => "zero", _ => 0 }; compila.
  • Ogni ramo deve produrre un valore, quindi un ramo non può chiamare un metodo void come Console.WriteLine, e una switch expression non può stare da sola come istruzione (CS0201). Per le azioni, usa l'istruzione switch.
  • Un ramo può lanciare un'eccezione invece di produrre un valore: _ => throw new ArgumentOutOfRangeException(nameof(input)).
  • Il compilatore rifiuta un ramo che non può mai essere raggiunto perché un ramo precedente lo copre già, con l'errore CS8510, The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. Metti i rami specifici prima di quelli generici.

Pattern relazionali e logici (C# 9)

C# 9 ha aggiunto <, <=, >, >= come pattern, e i combinatori and, or e not. Rendono le tabelle di intervalli leggibili come una specifica:

// C# 9 and later
string Grade(int score) => score switch
{
    < 0 or > 100 => throw new ArgumentOutOfRangeException(nameof(score)),
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _ => "F"
};

string AgeGroup(int age) => age switch
{
    < 13 => "child",
    >= 13 and <= 19 => "teen",
    _ => "adult"
};

bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

Dato che i rami vengono provati in ordine, >= 80 vede solo i punteggi sotto 90. I pattern relazionali accettano solo costanti sul lato destro; per confrontare con una variabile, usa una guardia when. L'equivalente in C# 7 di una tabella di intervalli è una catena di if:

Output:

95 -> A
83 -> B
71 -> C
64 -> D
12 -> F

not si usa più spesso come not null: x is not null, oppure un ramo not null => x.Name.

Pattern di proprietà

Un pattern di proprietà confronta i membri di un oggetto: { Property: pattern, ... }. Insieme a una switch expression, esprime le regole di business senza una catena di condizioni &&:

// C# 8 and later
decimal ShippingCost(Order order) => order switch
{
    { Total: >= 100m } => 0m,                       // relational inside: C# 9
    { Country: "US", Express: true } => 15m,
    { Country: "US" } => 5m,
    { Express: true } => 30m,
    null => throw new ArgumentNullException(nameof(order)),
    _ => 12m
};

Ogni proprietà elencata deve corrispondere perché il ramo corrisponda. Un pattern di proprietà vuoto { } corrisponde a qualsiasi valore non null. C# 10 ha aggiunto i pattern di proprietà estesi per i membri annidati: { Customer.Address.Country: "US" } invece di { Customer: { Address: { Country: "US" } } }.

Pattern di tuple

Fare lo switch su una tupla confronta più valori insieme, ed è il modo pulito per esprimere una tabella di decisione:

// C# 9 and later (the 'or' pattern)
string Winner(string a, string b) => (a, b) switch
{
    ("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "player 1",
    var (x, y) when x == y => "draw",
    _ => "player 2"
};

var (x, y) decostruisce la tupla in due variabili che la guardia when può confrontare. In C# 7 il sostituto abituale è combinare i valori in un'unica chiave, oppure usare un dizionario di esiti:

Output:

player 1
player 2
draw

Pattern di tipo e when

Un ramo può verificare il tipo a runtime e associare una variabile, e una clausola when aggiunge qualsiasi condizione che la sintassi dei pattern non riesce a esprimere:

// C# 8 and later
double Area(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle r when r.Width == r.Height => r.Width * r.Width,
    Rectangle r => r.Width * r.Height,
    null => throw new ArgumentNullException(nameof(shape)),
    _ => throw new NotSupportedException(shape.GetType().Name)
};

La versione C# 7 usa il pattern di tipo is in una catena di if, che associa la variabile nello stesso modo:

Output:

Circle: 12.57
Rectangle: 13.50

Esaustività e SwitchExpressionException

Una switch expression deve produrre un valore per ogni input. Quando il compilatore vede input che nessun ramo copre, avvisa:

warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered.

È un avviso, non un errore, quindi il codice compila comunque. Se a runtime arriva un valore senza corrispondenza, l'espressione lancia System.Runtime.CompilerServices.SwitchExpressionException, il cui messaggio termina con il valore non corrispondente (Unmatched value was 3.).

Gli enum sono un caso particolare. Anche quando ogni membro con nome ha un ramo, una variabile enum può contenere qualsiasi intero ((Size)3), quindi il compilatore segnala CS8524 per "un valore enum senza nome":

// C# 8 and later
enum Size { Small, Medium, Large }

string Code(Size s) => s switch
{
    Size.Small => "S",
    Size.Medium => "M",
    Size.Large => "L",
    // warning CS8524 without the next arm
    _ => throw new ArgumentOutOfRangeException(nameof(s))
};

Aggiungi un ramo _ esplicito che lanci un'eccezione utile. Questo documenta l'assunzione, elimina l'avviso e dà un errore più chiaro di SwitchExpressionException se mai dovesse comparire un valore sbagliato.

Un Dictionary come alternativa

Quando i rami sono semplici coppie costante-valore e la tabella potrebbe crescere o arrivare dalla configurazione, un Dictionary fa lo stesso lavoro in qualsiasi versione di C# e si può costruire a runtime:

Output:

19.00
11.50
0

Un paese sconosciuto ottiene un'aliquota di 0m. Un prodotto decimal ha tante cifre decimali quante ne hanno i suoi due operandi insieme, ed è per questo che 100m * 0.19m stampa 19.00 e l'aliquota zero stampa 0.

Switch expression e istruzione switch a confronto

istruzione switchswitch expression
Disponibile daC# 1C# 8
Produce un valorenosì
Sintassi per ramocase X: ... break;X => value,
Ripiegodefault:_ =>
Più istruzioni per ramosìno, chiama un metodo
Avviso di esaustivitànosì (CS8509)
Nessuna corrispondenza a runtimenon viene eseguito nullaSwitchExpressionException

Entrambe le forme accettano gli stessi pattern (tipi, costanti, proprietà, tuple, relazionali), quindi imparare l'una insegna anche l'altra. La scelta riguarda la forma: un valore passa per l'espressione, una sequenza di azioni passa per l'istruzione.

Domande frequenti

Cos'è una switch expression in C#?

Una switch expression, aggiunta in C# 8, produce un valore: string text = code switch { 200 => "OK", 404 => "Not Found", _ => "Unknown" };. L'input va prima della parola chiave switch, ogni ramo è pattern => result, i rami sono separati da virgole, e non ci sono le parole chiave case, break o default.

Qual è il caso default in una switch expression di C#?

Il pattern discard _ corrisponde a qualsiasi cosa, quindi _ => "Unknown" come ultimo ramo fa la parte di default. Senza un ramo generico, il compilatore avvisa (CS8509) quando qualche input non è coperto, e un valore senza corrispondenza a runtime lancia SwitchExpressionException.

Come faccio a gestire più valori in un solo ramo di una switch expression?

Usa il pattern or di C# 9: "sat" or "sun" => "weekend". Per gli intervalli, combina i pattern relazionali con and: >= 13 and <= 19 => "teen". In C# 8 elencheresti i valori come rami separati con lo stesso risultato.

Posso usare when in una switch expression di C#?

Sì. Un ramo può aggiungere una guardia dopo il suo pattern: Order o when o.Total > 100 => 0m. Il ramo corrisponde solo se il pattern corrisponde e la condizione when è vera; altrimenti la valutazione prosegue con il ramo successivo.

Meglio una switch expression o un'istruzione switch?

Usa l'espressione quando ogni ramo produce solo un valore, per esempio per associare uno stato a un'etichetta o calcolare un prezzo. Usa l'istruzione quando i rami eseguono più istruzioni, hanno effetti collaterali o devono uscire da un ciclo. L'espressione ti dà anche avvisi di esaustività che l'istruzione non dà.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA