Masz metodę z kilkoma warunkami, a każdy z nich ma zwrócić inny status, komunikat albo obiekt? Wyrażenie switch w C# zwykle porządkuje taki wybór, choć przy wartościach null i niedopasowanych danych łatwo przeoczyć istotny przypadek.

Najlepiej używać switch expression, gdy każda gałąź zwraca jedną wartość
- Wynik zamiast instrukcji: każda ramka wyrażenia przypisuje lub zwraca wartość.
- Kolejność ma znaczenie: wygrywa pierwsza pasująca gałąź, której warunek
whenjest prawdziwy. - Wzorzec odrzucający
_: obsługuje pozostałe przypadki, ale może też zamaskować nieoczekiwane dane. - Wzorce dopasowania: wzorce typów, właściwości i zakresów pozwalają ograniczyć rozbudowane łańcuchy
if.
Switch expression pasuje do wyboru jednej wartości
Najłatwiej rozpoznać dobre zastosowanie po lewej stronie przypisania: jeśli wynik ma być pojedynczą wartością, wyrażenie switch często czytelnie zastępuje łańcuch if. Wprowadzono je w C# 8; każda gałąź, nazywana ramieniem, ma wzorzec i wyrażenie wyniku.
static string GetLabel(int status) =>
status switch
{
200 => "OK",
404 => "Nie znaleziono",
500 => "Błąd serwera",
_ => "Nieznany status"
};W tym przykładzie każda ścieżka zwraca tekst, więc zapis można wprost wykorzystać w przypisaniu, instrukcji return albo jako część większego wyrażenia. Microsoft opisuje tę formę jako sposób na wybranie wartości na podstawie pierwszego pasującego wzorca w dokumentacji wyrażenia switch w C#.
Granica jest praktyczna: gdy gałąź ma wykonać kilka działań, zapisać log, zmienić stan i dopiero potem zakończyć metodę, zwykła instrukcja switch może być czytelniejsza. Uważam, że zwięzłość pomaga tylko wtedy, gdy odbiorca od razu widzi, skąd bierze się wynik.
Wzorce typów i właściwości zamieniają if na opis danych
Przy obiektach sama wartość często nie wystarcza. Potrzebujesz rozpoznać typ i sprawdzić jego właściwości, a wzorzec właściwości pozwala połączyć te testy bez osobnych warunków zagnieżdżonych.
static string DeliveryMessage(Delivery? delivery) =>
delivery switch
{
null => "Brak dostawy",
ExpressDelivery express =>
$"Przesyłka ekspresowa: {express.TrackingCode}",
StandardDelivery { Days: <= 2 } =>
"Dostawa standardowa wkrótce",
StandardDelivery =>
"Dostawa standardowa",
_ => "Inny rodzaj dostawy"
};ExpressDelivery express sprawdza typ i udostępnia dopasowany obiekt pod nazwą express. Z kolei StandardDelivery { Days: <= 2 } łączy rozpoznanie typu z warunkiem na właściwości. Takie dopasowanie bywa bardziej czytelne niż kilka poziomów if, gdy przypadki wynikają z kształtu danych.
Przy bardziej złożonym warunku możesz dodać when, czyli dodatkowy warunek logiczny po wzorcu. Przykładowo StandardDelivery delivery when delivery.Days > limit może rozdzielić zamówienia według czasu dostawy, ale gdy warunek da się zapisać bezpośrednio jako wzorzec właściwości, krótsza forma zwykle łatwiej się skanuje.
Zakresy i kolejność ramion rozstrzygają granice
Gdy wiele wartości liczbowych prowadzi do tych samych wyników, wzorce relacyjne zapisują przedziały bez powtarzania każdej liczby. Trzeba jednak patrzeć na kolejność od góry: pierwsze pasujące ramię kończy dopasowanie.
static string GetTemperatureLabel(int celsius) =>
celsius switch
{
< 0 => "Mróz",
0 => "Punkt zamarzania",
> 0 and < 30 => "Umiarkowana temperatura",
_ => "Upał"
};Wzorce relacyjne, takie jak < 0, oraz logiczne, takie jak > 0 and < 30, są częścią współczesnego dopasowania wzorców w C#. Jeśli wzorce nachodzą na siebie, bardziej szczegółowy warunek umieść przed ogólnym. Ramię _ umieszczone wcześniej przechwyci wszystko i kolejne gałęzie staną się nieosiągalne. Więcej o łączeniu zakresów opisuje dokumentacja Microsoft dotycząca wzorców relacyjnych i logicznych.
Wybór wzorca zależy też od wersji języka: wzorce relacyjne i logiczne pojawiły się w C# 9, a wzorce list w C# 11. Jeśli projekt ma starszą wersję języka, składnia może nie zostać rozpoznana, nawet gdy kod wygląda poprawnie na nowszym przykładzie.
Brakująca gałąź może ujawnić się dopiero podczas działania
Tu kryje się częsta pułapka: ostrzeżenie kompilatora nie daje gwarancji, że każda możliwa kombinacja wartości została obsłużona. Gdy żadne ramię nie pasuje, wyrażenie może zakończyć się wyjątkiem SwitchExpressionException, zamiast po cichu zwrócić wartość domyślną.
| Sytuacja | Możliwy skutek | Co zrobić |
|---|---|---|
Typ wejściowy dopuszcza null, ale nie ma ramienia null |
Niejasna obsługa pustego wejścia lub ostrzeżenie o niepełnym dopasowaniu | Dodaj null => ... albo jawnie odrzuć null przed dopasowaniem |
| Enum ma nieoczekiwaną wartość liczbową | Żadna z nazwanych wartości wyliczenia nie pasuje | Dodaj _ z wynikiem awaryjnym albo rzuć wyjątek z czytelnym komunikatem |
| Ogólny wzorzec pojawia się przed szczegółowym | Późniejsze ramię może być nieosiągalne | Przenieś szeroki wzorzec na koniec i sprawdź ostrzeżenia kompilatora |
| Nowy typ dziedziczący nie pasuje do znanych typów | Nieobsłużona gałąź podczas działania programu | Przewidź zachowanie dla nieznanego typu albo jawnie zgłoś błąd |
Przy enumie warto odróżnić dwie intencje. Jeśli każda nieznana wartość ma dostać bezpieczny, użyteczny wynik, ramię _ może być właściwe; jeśli nowa wartość oznacza błąd wymagający poprawki, lepszy będzie wyjątek niż etykieta „nieznany”. Microsoft zwraca uwagę, że kompilator ostrzega o wykrytych lukach i nakładających się wzorcach, ale nie potrafi dowieść kompletności dla każdej kombinacji.
Wybierz wyrażenie dla mapowania, instrukcję dla działań
Prosty test decyzyjny brzmi: czy każda gałąź ma dostarczyć jedną wartość? Jeśli tak, wyrażenie zwykle dobrze pasuje. Jeśli każda ścieżka wykonuje serię operacji lub ma kilka kroków, zostań przy instrukcji switch albo wydziel logikę do osobnych metod.
- Mapowanie statusu na etykietę: użyj wyrażenia, bo każda wartość wejściowa daje jeden tekst.
- Wybór implementacji według typu: zastosuj wzorce typów, gdy każda klasa wymaga innego wyniku.
- Walidacja zakresu: użyj wzorców relacyjnych, jeśli przedziały są krótkie i nie nachodzą na siebie.
- Obsługa skutków ubocznych: wybierz instrukcję, gdy gałąź zapisuje kilka zmian lub wykonuje wiele niezależnych działań.
Jeśli warunki zaczynają przypominać mały silnik reguł, zagnieżdżają się albo używają tych samych obliczeń w wielu ramionach, skrócenie zapisu może utrudnić utrzymanie kodu. Lepiej wyciągnąć obliczenie do nazwanej metody i zostawić w wyrażeniu sam wybór.
Dobry switch expression kończy się świadomym przypadkiem awaryjnym
Najczytelniejsze dopasowanie zaczyna się od przypadków szczegółowych, jawnie obejmuje null tam, gdzie może się pojawić, i kończy obsługą reszty. Ramię domyślne powinno wyrażać decyzję projektową, a nie służyć wyłącznie do uciszenia ostrzeżenia.
Gdy brakująca wartość jest normalna, zwróć sensowny wynik awaryjny. Gdy świadczy o błędzie danych albo nieaktualnym założeniu w kodzie, zgłoś problem. Wtedy zapis pozostaje krótki, a zachowanie programu nie zaskoczy cię dopiero w produkcji.
