Switch w C# – od klasycznej instrukcji po pattern matching
if (status == "Nowe") { /* ... */ }
else if (status == "Oplacone") { /* ... */ }
else if (status == "Wyslane") { /* ... */ }
else if (status == "Anulowane") { /* ... */ }
else { /* ... */ }
Cztery gałęzie tej samej zmiennej, powtórzone porównanie za powtórzonym porównaniem. switch robi to samo, czytelniej — a w nowoczesnym C# potrafi też dużo więcej niż tylko porównywać równość.
Klasyczna instrukcja switch
switch (status)
{
case "Nowe":
Console.WriteLine("Czeka na płatność");
break;
case "Oplacone":
Console.WriteLine("Przygotowywane do wysyłki");
break;
case "Wyslane":
case "Dostarczone": // dwie wartości → ta sama gałąź
Console.WriteLine("W drodze lub dostarczone");
break;
default:
Console.WriteLine("Nieznany status");
break;
}
Ważna różnica względem C, C++ czy JavaScript: C# nie pozwala na niejawne przechodzenie między gałęziami (fall-through). Każdy case z kodem musi kończyć się break, return, continue albo goto case — kod, który “przecieka” do kolejnej gałęzi bez żadnego z tych słów, po prostu nie skompiluje się. To zabezpieczenie przed jednym z klasyczniejszych bugów w językach C-podobnych.
case "Wyslane":
Console.WriteLine("W drodze");
goto case "Oplacone"; // ✅ jawne przejście do innej gałęzi — trzeba to napisać wprost
Switch expression — zwraca wartość, nie tylko wykonuje kod
Od C# 8 masz zwięźlejszą wersję, która zwraca wartość zamiast wykonywać instrukcje:
string opis = status switch
{
"Nowe" => "Czeka na płatność",
"Oplacone" => "Przygotowywane do wysyłki",
"Wyslane" or "Dostarczone" => "W drodze lub dostarczone", // 'or' zamiast dwóch case-ów
_ => "Nieznany status" // _ to odpowiednik default
};
Zero break, zero duplikacji nazwy zmiennej w każdej gałęzi, i — kluczowa różnica — to wyrażenie, nie instrukcja: możesz je przypisać bezpośrednio do zmiennej, zwrócić z metody, przekazać jako argument. Klasyczny switch tylko wykonuje kod; switch expression produkuje wartość.
Pattern matching — switch na typie, nie tylko na wartości
Prawdziwa siła nowoczesnego switch w C# to dopasowywanie wzorców, nie tylko dokładnych wartości:
string OpisKsztaltu(object ksztalt) => ksztalt switch
{
Kolo k when k.Promien > 100 => "Duże koło",
Kolo k => $"Koło o promieniu {k.Promien}",
Prostokat { Szerokosc: var s, Wysokosc: var w } when s == w => "Kwadrat", // wzorzec właściwości
Prostokat p => $"Prostokąt {p.Szerokosc}x{p.Wysokosc}",
null => "Brak kształtu",
_ => "Nieznany kształt"
};
Ten jeden switch łączy trzy rodzaje dopasowania naraz:
- wzorzec typu (
Kolo k) — sprawdza, czyksztaltjest typuKolo, i od razu rzutuje na zmiennąk, - warunek
when— dodatkowy filtr sprawdzany tylko, gdy typ pasuje, - wzorzec właściwości (
Prostokat { Szerokosc: var s, ... }) — dopasowuje na podstawie wartości konkretnych właściwości obiektu.
Wzorce relacyjne i zakresowe
string Ocena(int punkty) => punkty switch
{
>= 90 => "Celujący",
>= 75 => "Bardzo dobry",
>= 50 => "Dostateczny",
< 0 => throw new ArgumentException("Punkty nie mogą być ujemne"),
_ => "Niedostateczny"
};
Operatory relacyjne (>=, <, >) wewnątrz switch expression zastępują to, co w starszym C# wymagałoby serii if sprawdzających zakresy — i, tak jak zwykły switch, są sprawdzane w kolejności od góry, więc kolejność gałęzi ma znaczenie (pierwsza pasująca wygrywa).
Wzorzec krotki — dopasowanie kilku wartości naraz
string Wskazowka(int x, int y) => (x, y) switch
{
(0, 0) => "Środek",
(0, _) => "Na osi Y",
(_, 0) => "Na osi X",
(var px, var py) when px == py => "Na przekątnej",
_ => "Gdzieś indziej"
};
Pakując kilka wartości w krotkę (x, y), dopasowujesz kombinację naraz — bez zagnieżdżonych if-ów sprawdzających każdą wartość osobno. Znak _ w krotce oznacza “dowolna wartość na tej pozycji”, dokładnie jak _ jako cała gałąź domyślna.
Pułapka: brak wyjątku, gdy żadna gałąź nie pasuje
int liczbaNog = zwierze switch
{
Pies => 4,
Ptak => 2,
// brak _ (default)
};
// jeśli zwierze to Ryba, a nie Pies/Ptak:
// System.Runtime.CompilerServices.SwitchExpressionException w runtime
Bez gałęzi _, switch expression rzuca SwitchExpressionException, jeśli żadna gałąź nie pasuje — kompilator ostrzeże Cię o tym z wyprzedzeniem (CS8509 “the switch expression does not handle all possible values”), ale to tylko warning, nie błąd — kod się skompiluje i wybuchnie dopiero w runtime na nieoczekiwanym wejściu. Zawsze dodawaj _ =>, chyba że świadomie chcesz, żeby nieobsłużony przypadek był wyjątkiem (np. przy enum, gdzie nowa wartość powinna głośno wywalić kompilację ostrzeżeniem, a nie cicho przejść).
switch statement kontra switch expression — kiedy który
| switch statement | switch expression | |
|---|---|---|
| Co robi | wykonuje instrukcje | zwraca wartość |
| Składnia | case x: ... break; | x => wartość |
| Wiele linii kodu na gałąź | ✅ naturalne | ⚠️ wymaga bloku { } z return wewnątrz |
| Przypisanie wyniku wprost | ❌ trzeba osobnej zmiennej + przypisań w każdej gałęzi | ✅ var wynik = x switch {...} |
Reguła praktyczna: jeśli każda gałąź kończy się tym samym — “zwróć wartość” albo “przypisz coś do zmiennej” — sięgaj po switch expression, jest krótszy i mniej podatny na literówkę w postaci zapomnianego przypisania w jednej z gałęzi. Jeśli gałęzie wykonują różne, wieloliniowe akcje (logowanie, wywołania I/O, efekty uboczne) — klasyczny switch statement bywa czytelniejszy.
Co dalej
Jeśli martwi Cię wydajność switch expression względem drabiny if-ów (spoiler: kompilator generuje porównywalny kod, różnica jest pomijalna) albo chcesz zobaczyć więcej o zastępowaniu długich if-ów w praktyce, sprawdź Pattern Matching w C# — pogłębia dokładnie ten temat.
Podsumowanie
Klasyczny switch w C# nie pozwala na niejawne przechodzenie między gałęziami — to zabezpieczenie, nie ograniczenie. switch expression (C# 8+) zwraca wartość zamiast wykonywać instrukcje, co eliminuje powtarzanie nazwy zmiennej i literówki w przypisaniach. Prawdziwa moc leży w pattern matching: dopasowanie po typie, warunki when, wzorce właściwości i relacyjne pozwalają zastąpić długie łańcuchy if/else czytelną, deklaratywną strukturą — pamiętaj tylko o gałęzi _, żeby nieobsłużony przypadek był świadomą decyzją, nie wyjątkiem w runtime.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →