Die C# switch-Anweisung wählt anhand eines Werts oder Musters zwischen mehreren Codeblöcken aus – eine strukturierte Alternative zu einer langen Kette von if/else if. Seit C# 8 gibt es zusätzlich den switch-Ausdruck, der direkt einen Wert liefert. Dieser Beitrag zeigt beide Varianten, Pattern Matching mit case-Guards, den Umgang mit null und dem default-Fall sowie die häufigsten Fehler – jeweils mit Eingabe und tatsächlicher Ausgabe.
Switch-Anweisung oder Switch-Ausdruck?
Beide bauen auf derselben Mustersprache auf, unterscheiden sich aber in Syntax und Verhalten:
| Merkmal | switch-Anweisung | switch-Ausdruck |
|---|---|---|
| Syntax je Fall | case Wert: … break; | Muster => Ergebnis |
| Gibt einen Wert zurück | Nein – reine Anweisung | Ja – direkt einsetzbar, z. B. bei einer Zuweisung |
| Ohne passenden Fall | Läuft ohne Aktion durch, wenn kein default existiert | Wirft eine SwitchExpressionException, wenn kein Discard-Muster _ existiert |
| Mehrere Anweisungen je Fall | Beliebig viele Anweisungen möglich | Genau ein Ausdruck je Fall |
| Seit | C# 1, mit Pattern Matching erweitert seit C# 7 | C# 8 |
Grundsyntax der Switch-Anweisung
string wochentag = "Mittwoch";
switch (wochentag)
{
case "Montag":
Console.WriteLine("Start in die Woche.");
break;
case "Mittwoch":
Console.WriteLine("Bergfest.");
break;
case "Freitag":
Console.WriteLine("Fast Wochenende.");
break;
default:
Console.WriteLine("Ganz normaler Tag.");
break;
}
Ausgabe:
Bergfest.
Wichtig: Anders als in C, Java oder JavaScript erlaubt der C#-Compiler kein stilles Durchfallen von einem Fall in den nächsten. Jeder Abschnitt muss ausdrücklich mit break, return, throw oder goto case enden – fehlt das, meldet der Compiler einen Fehler, statt den Code stillschweigend durchlaufen zu lassen.
Mehrere Fälle zusammenfassen
Mehrere case-Marken dürfen sich denselben Codeblock teilen, solange die oberen Marken leer bleiben:
int monat = 4;
switch (monat)
{
case 12:
case 1:
case 2:
Console.WriteLine("Winter");
break;
case 3:
case 4:
case 5:
Console.WriteLine("Frühling");
break;
default:
Console.WriteLine("Andere Jahreszeit");
break;
}
Ausgabe (für monat = 4):
Frühling
Das ist kein Widerspruch zum Fallthrough-Verbot: Die leeren Marken case 12: und case 1: enthalten selbst keine Anweisungen, es gibt also nichts, was „durchfallen“ könnte. Erst die letzte Marke der Gruppe trägt den Codeblock.
Der Switch-Ausdruck
Soll das Ergebnis direkt einer Variablen zugewiesen oder zurückgegeben werden, ist der switch-Ausdruck meist kürzer. Der Kombinator or ersetzt dabei die gestapelten case-Marken:
static string Jahreszeit(int monat) => monat switch
{
12 or 1 or 2 => "Winter",
3 or 4 or 5 => "Frühling",
6 or 7 or 8 => "Sommer",
9 or 10 or 11 => "Herbst",
_ => throw new ArgumentOutOfRangeException(nameof(monat), "Monat muss zwischen 1 und 12 liegen.")
};
Console.WriteLine(Jahreszeit(4));
Console.WriteLine(Jahreszeit(11));
Ausgabe:
Frühling
Herbst
Der Unterstrich _ ist das Discard-Muster und übernimmt hier die Rolle von default. Lässt du ihn weg und es trifft kein anderer Fall zu, wirft die Laufzeit eine System.Runtime.CompilerServices.SwitchExpressionException – anders als bei der switch-Anweisung gibt es hier kein stilles „passiert nichts“.
Pattern Matching: mehr als feste Werte
Sowohl Anweisung als auch Ausdruck akzeptieren seit C# 9 relationale Muster (>= 90, < 0 und so weiter). Die Muster werden von oben nach unten geprüft, der erste Treffer gewinnt – die Reihenfolge muss deshalb vom spezielleren zum allgemeineren Muster gehen:
int punkte = 82;
string note = punkte switch
{
>= 90 => "Sehr gut",
>= 75 => "Gut",
>= 50 => "Befriedigend",
>= 0 => "Nicht bestanden",
_ => "Ungültige Punktzahl"
};
Console.WriteLine(note);
Ausgabe:
Gut
Zusätzlich lassen sich mit dem Schlüsselwort when beliebige Bedingungen anhängen (Case-Guard), und mit runden Klammern mehrere Werte gemeinsam als Tupel-Muster prüfen:
int punkte = 82;
bool mitglied = true;
switch ((punkte, mitglied))
{
case ( >= 50, true):
Console.WriteLine("Bestanden, Mitgliedsrabatt gültig.");
break;
case ( >= 50, false):
Console.WriteLine("Bestanden, kein Rabatt.");
break;
default:
Console.WriteLine("Nicht bestanden.");
break;
}
Ausgabe:
Bestanden, Mitgliedsrabatt gültig.
Der default-Fall
default greift in der switch-Anweisung, wenn kein anderer Fall zutrifft. Er darf an beliebiger Stelle im switch-Block stehen, wird aber inhaltlich erst geprüft, nachdem alle anderen Fälle durchgegangen sind – oder wenn der Code ausdrücklich per goto default; dorthin springt. Im switch-Ausdruck übernimmt das Discard-Muster _ dieselbe Rolle, mit einem wichtigen Unterschied: Fehlt es und es passt kein Fall, entsteht keine stille Nichtaktion, sondern eine Exception zur Laufzeit.
Null behandeln
string? status = null;
switch (status)
{
case "aktiv":
Console.WriteLine("Aktiv.");
break;
case null:
Console.WriteLine("Kein Status gesetzt.");
break;
default:
Console.WriteLine("Unbekannter Status.");
break;
}
Ausgabe:
Kein Status gesetzt.
case null: ist ein ganz normales Konstantenmuster und funktioniert sowohl bei nullbaren Referenztypen wie string? als auch bei nullbaren Werttypen wie int?. Ohne einen expliziten case null: fällt ein null-Wert einfach in default – anders als in manchen anderen Sprachen wirft C# dabei keine automatische Ausnahme.
Häufige Fehler
Fehlendes break in der Anweisung
Wer aus Gewohnheit aus einer anderen Sprache das break vergisst, bekommt in C# keinen stillen Fehler, sondern einen Compilerfehler: Der Compiler verlangt für jeden Fall ein eindeutiges Ende (break, return, throw oder goto case). Willst du bewusst mehrere Fälle mit demselben Code verbinden, ohne die Marken direkt untereinander zu stapeln, hilft goto case „Wert“;.
Switch-Ausdruck ohne Discard-Muster
Fehlt der Fall _ im switch-Ausdruck und tritt zur Laufzeit ein Wert auf, den kein Muster abdeckt, stürzt das Programm mit einer SwitchExpressionException ab. Plane _ deshalb grundsätzlich ein, außer du kannst nachweislich alle Fälle abdecken – etwa bei einem vollständig durchnummerierten enum.
Falsche Reihenfolge bei Pattern Matching
Da Muster von oben nach unten geprüft werden, muss das speziellere Muster vor dem allgemeineren stehen. Steht >= 0 => "Nicht bestanden" vor >= 50 => "Befriedigend", greift immer schon der erste, zu allgemeine Fall – der Compiler meldet solche unerreichbaren Muster in der Regel als Warnung oder Fehler.
Variablen als case-Wert
Klassische case-Werte müssen zur Kompilierzeit bekannte Konstanten sein – eine Variable als case meineVariable: lässt der Compiler nicht zu. Willst du gegen einen Laufzeitwert prüfen, brauchst du ein Pattern mit when-Guard, wie im Abschnitt zu Pattern Matching weiter oben.
Switch oder if/else: wann was?
Als Faustregel: Viele feste Werte oder Muster, die zu einem einzigen Ergebnis führen, sprechen für switch – besonders als Ausdruck, wenn das Ergebnis direkt weiterverwendet wird. Wenige Bedingungen oder komplexe logische Verknüpfungen mehrerer unabhängiger Variablen bleiben mit if/else oft besser lesbar. Die Grundlagen zu if/else stehen im Beitrag C# lernen.
Wie es weitergeht
Wenn du bei den Grundlagen einsteigen willst, hilft der Beitrag C# lernen: Einstieg, erstes Programm und Lernpfad. Für den Unterschied zwischen Feld und Property lohnt sich C# Property: get, set, init und required verstehen, und für den Umgang mit Zeichenketten C# String Split sowie C# String to int.
Häufige Fragen
Muss ich in jedem case-Block ein break setzen?
In der switch-Anweisung ja, sofern der Block nicht bereits mit return, throw oder goto case endet. Im switch-Ausdruck entfällt das komplett, weil dort jeder Fall genau ein Ergebnis liefert und es kein Durchfallen gibt.
Kann ich mit switch auch Strings vergleichen?
Ja. Strings lassen sich genauso wie Zahlen oder Enum-Werte als case-Wert verwenden, wie das Beispiel mit dem Wochentag oben zeigt.
Was passiert, wenn kein Fall zutrifft und es keinen default- oder Discard-Fall gibt?
Bei der switch-Anweisung passiert dann schlicht nichts – der Code läuft ohne Aktion hinter dem switch-Block weiter. Beim switch-Ausdruck ist das anders: Dort wirft die Laufzeit eine SwitchExpressionException, weil ein Ausdruck immer einen Wert liefern muss.
Ist der switch-Ausdruck schneller als die switch-Anweisung?
Dafür gibt es keine pauschale Garantie. Der Compiler übersetzt beide Formen in ähnliche Sprunglogik; in der Praxis ist der Unterschied für normalen Anwendungscode meist nicht relevant. Die Wahl sollte sich in erster Linie an der Lesbarkeit orientieren.
Ab welcher C#-Version gibt es Pattern Matching im switch?
Type Patterns im switch gibt es seit C# 7, der switch-Ausdruck kam mit C# 8 dazu, und relationale sowie logische Muster (and, or, not, Vergleichsoperatoren) seit C# 9. Aktuell ist C# 14, ausgeliefert mit .NET 10.