🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

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ę breakreturncontinue 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, czy ksztalt jest typu Kolo, 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 statementswitch expression
Co robiwykonuje instrukcjezwraca wartość
Składniacase 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.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 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.

Dodaj komentarz

czytanie to początek

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ę →