Menu

C# switch-Ausdruck: Syntax, Muster, Discards und when

Der switch-Ausdruck aus C# 8 macht mit Muster-Armen statt case und break aus einem Wert ein Ergebnis. Syntax, der Discard _, Property-, Tupel- und relationale Muster, and/or/not, when-Guards, Warnungen zur Vollständigkeit und SwitchExpressionException, mit ausführbaren Entsprechungen in C# 7.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Ein switch-Ausdruck bildet einen Eingabewert auf ein Ergebnis ab. Er kam mit C# 8 und ersetzt das verbreitete Muster einer switch-Anweisung, in der jeder Fall nur etwas zurückgibt oder zuweist:

// 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"
};

Lies ihn von oben nach unten: Der Wert vor switch wird der Reihe nach gegen jeden Arm geprüft, und der erste Arm, dessen Muster passt, liefert das Ergebnis. Es gibt kein case, kein break und kein Schlüsselwort default; _ (das Discard-Muster) passt auf alles und dient als Rückfall.

In C# 7 ist dieselbe Abbildung eine switch-Anweisung in einer kleinen Methode, mit return in jedem Fall:

Ausgabe:

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

Die Ausdrucksform ist vor allem kürzer, weil jeder Arm eine Zeile ist und der Methodenrumpf wegfällt. Außerdem ist sie ein Ausdruck und kann überall stehen, wo ein Wert stehen kann: als Argument, in einem Interpolationsloch, in einem return, in einem Feldinitialisierer.

Syntaxregeln

var result = input switch
{
    pattern1 => value1,
    pattern2 when condition => value2,
    _ => fallback
};
  • Zuerst kommt die Eingabe, dann switch. Das ist die umgekehrte Reihenfolge zur Anweisungsform.
  • Jeder Arm lautet pattern => expression. Die rechte Seite muss ein einzelner Ausdruck sein; für mehr Logik rufe eine Methode auf.
  • Arme werden durch Kommas getrennt. Ein Komma nach dem letzten Arm ist erlaubt.
  • Das Ganze ist ein Ausdruck, eine Anweisung, die ihn verwendet, endet also nach der schließenden Klammer mit ;.
  • Alle Arme müssen Werte eines gemeinsamen Typs erzeugen. var x = n switch { 0 => "zero", _ => 0 }; kompiliert nicht (CS8506, kein bester Typ). Seit C# 9 entscheidet ein deklarierter Zieltyp: object x = n switch { 0 => "zero", _ => 0 }; kompiliert.
  • Jeder Arm muss einen Wert erzeugen, ein Arm kann also keine void-Methode wie Console.WriteLine aufrufen, und ein switch-Ausdruck kann nicht allein als Anweisung stehen (CS0201). Für Aktionen nimm die switch-Anweisung.
  • Ein Arm kann werfen, statt einen Wert zu erzeugen: _ => throw new ArgumentOutOfRangeException(nameof(input)).
  • Der Compiler lehnt einen Arm ab, der nie erreicht werden kann, weil ein früherer Arm ihn schon abdeckt, mit Fehler CS8510, The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. Setze spezifische Arme vor allgemeine.

Relationale und logische Muster (C# 9)

C# 9 hat <, <=, >, >= als Muster hinzugefügt, dazu die Kombinatoren and, or und not. Damit lesen sich Bereichstabellen wie eine Spezifikation:

// 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';

Weil die Arme der Reihe nach geprüft werden, sieht >= 80 nur Punktzahlen unter 90. Relationale Muster nehmen rechts nur Konstanten; um mit einer Variable zu vergleichen, nimm einen when-Guard. Die Entsprechung einer Bereichstabelle in C# 7 ist eine if-Kette:

Ausgabe:

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

not wird am häufigsten als not null verwendet: x is not null oder ein Arm not null => x.Name.

Property-Muster

Ein Property-Muster prüft die Member eines Objekts: { Property: pattern, ... }. Zusammen mit einem switch-Ausdruck drückt es Geschäftsregeln ohne eine Kette von &&-Bedingungen aus:

// 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
};

Jede aufgeführte Property muss passen, damit der Arm passt. Ein leeres Property-Muster { } passt auf jeden Wert außer null. C# 10 hat erweiterte Property-Muster für verschachtelte Member hinzugefügt: { Customer.Address.Country: "US" } statt { Customer: { Address: { Country: "US" } } }.

Tupel-Muster

Ein switch auf ein Tupel prüft mehrere Werte gleichzeitig und ist der saubere Weg, eine Entscheidungstabelle auszudrücken:

// 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) dekonstruiert das Tupel in zwei Variablen, die der when-Guard vergleichen kann. In C# 7 ist der übliche Ersatz, die Werte zu einem Schlüssel zu kombinieren oder ein Dictionary mit Ergebnissen zu verwenden:

Ausgabe:

player 1
player 2
draw

Typmuster und when

Ein Arm kann den Laufzeittyp prüfen und eine Variable binden, und eine when-Klausel ergänzt jede Bedingung, die die Mustersyntax nicht ausdrücken kann:

// 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)
};

Die Version für C# 7 nutzt das is-Typmuster in einer if-Kette, das die Variable auf dieselbe Weise bindet:

Ausgabe:

Circle: 12.57
Rectangle: 13.50

Vollständigkeit und SwitchExpressionException

Ein switch-Ausdruck muss für jede Eingabe einen Wert erzeugen. Wenn der Compiler Eingaben sieht, auf die kein Arm passt, warnt er:

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.

Das ist eine Warnung, kein Fehler, der Code wird also trotzdem gebaut. Kommt zur Laufzeit ein nicht passender Wert an, wirft der Ausdruck System.Runtime.CompilerServices.SwitchExpressionException, deren Meldung mit dem nicht passenden Wert endet (Unmatched value was 3.).

Enums sind ein Sonderfall. Selbst wenn jedes benannte Member einen Arm hat, kann eine Enum-Variable jede Ganzzahl enthalten ((Size)3), daher meldet der Compiler CS8524 für „an unnamed enum value“:

// 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))
};

Ergänze einen expliziten _-Arm, der eine aussagekräftige Exception wirft. Das dokumentiert die Annahme, beseitigt die Warnung und liefert einen klareren Fehler als SwitchExpressionException, falls je ein ungültiger Wert auftaucht.

Ein Dictionary als Alternative

Wenn die Arme reine Paare aus Konstante und Wert sind und die Tabelle wachsen oder aus der Konfiguration kommen könnte, erledigt ein Dictionary dieselbe Aufgabe in jeder C#-Version und lässt sich zur Laufzeit aufbauen:

Ausgabe:

19.00
11.50
0

Ein unbekanntes Land bekommt den Satz 0m. Ein Produkt von decimal-Werten hat so viele Nachkommastellen wie seine beiden Operanden zusammen, deshalb gibt 100m * 0.19m den Wert 19.00 aus und der Nullsatz 0.

switch-Ausdruck gegenüber switch-Anweisung

switch-Anweisungswitch-Ausdruck
Verfügbar seitC# 1C# 8
Erzeugt einen Wertneinja
Syntax pro Zweigcase X: ... break;X => value,
Rückfalldefault:_ =>
Mehrere Anweisungen pro Zweigjanein, rufe eine Methode auf
Warnung zur Vollständigkeitneinja (CS8509)
Kein Treffer zur Laufzeitnichts läuftSwitchExpressionException

Beide Formen nehmen dieselben Muster (Typen, Konstanten, Properties, Tupel, relational), wer eine lernt, lernt also auch die andere. Die Wahl ist eine Frage der Form: Ein Wert geht durch den Ausdruck, eine Folge von Aktionen durch die Anweisung.

Häufig gestellte Fragen

Was ist ein switch-Ausdruck in C#?

Ein switch-Ausdruck, eingeführt in C# 8, ergibt einen Wert: string text = code switch { 200 => "OK", 404 => "Not Found", _ => "Unknown" };. Die Eingabe steht vor dem Schlüsselwort switch, jeder Arm lautet pattern => result, Arme werden durch Kommas getrennt, und es gibt kein Schlüsselwort case, break oder default.

Was ist der default-Fall in einem C#-switch-Ausdruck?

Das Discard-Muster _ passt auf alles, _ => "Unknown" als letzter Arm übernimmt also die Rolle von default. Ohne Auffangarm warnt der Compiler (CS8509), wenn eine Eingabe nicht abgedeckt ist, und ein nicht passender Wert wirft zur Laufzeit SwitchExpressionException.

Wie prüfe ich mehrere Werte in einem Arm eines switch-Ausdrucks?

Nimm das or-Muster aus C# 9: "sat" or "sun" => "weekend". Für Bereiche kombiniere relationale Muster mit and: >= 13 and <= 19 => "teen". In C# 8 würdest du die Werte als separate Arme mit demselben Ergebnis auflisten.

Kann ich when in einem C#-switch-Ausdruck verwenden?

Ja. Ein Arm kann nach seinem Muster einen Guard ergänzen: Order o when o.Total > 100 => 0m. Der Arm passt nur, wenn das Muster passt und die when-Bedingung wahr ist; sonst geht die Auswertung mit dem nächsten Arm weiter.

Soll ich einen switch-Ausdruck oder eine switch-Anweisung verwenden?

Nimm den Ausdruck, wenn jeder Zweig nur einen Wert erzeugt, etwa einen Status auf eine Beschriftung abbilden oder einen Preis berechnen. Nimm die Anweisung, wenn Zweige mehrere Anweisungen ausführen, Nebeneffekte haben oder eine Schleife verlassen müssen. Der Ausdruck gibt dir außerdem Warnungen zur Vollständigkeit, die die Anweisung nicht bietet.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S