Przejdź do treści
School IT / Przestrzeń do nauki
← C# od podstawLekcja 15 z 58

Instrukcja switch

Gdy program ma wybrać jedną z wielu opcji na podstawie jednej wartości — numeru miesiąca, litery menu, kodu operacji — drabinka else if robi się długa i powtarzalna. Od tego jest switch: ta sama decyzja zapisana krócej i czytelniej.

C# switch wyrażenie switch 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Zamienisz długą drabinkę else if na instrukcję switch
  • Wyjaśnisz, dlaczego w C# nie wolno pominąć break
  • Obsłużysz kilka wartości jednym blokiem dzięki etykietom zbiorczym
  • Zapiszesz przypisanie krócej, używając wyrażenia switch
  • Rozpoznasz sytuacje, w których switch się nie nadaje
TEORIA

Instrukcja switch

switch bierze jedną wartość i porównuje ją po kolei z etykietami case. Gdy znajdzie zgodną, wykonuje instrukcje spod niej.

składnia
switch (wyrazenie)
{
    case wartosc1:
        // instrukcje
        break;

    case wartosc2:
        // instrukcje
        break;

    default:
        // gdy nic nie pasowalo
        break;
}

Porównaj te same reguły zapisane na dwa sposoby:

drabinka else if
int dzien = 3;

if (dzien == 1)      { Console.WriteLine("poniedzialek"); }
else if (dzien == 2) { Console.WriteLine("wtorek"); }
else if (dzien == 3) { Console.WriteLine("sroda"); }
else if (dzien == 4) { Console.WriteLine("czwartek"); }
else if (dzien == 5) { Console.WriteLine("piatek"); }
else                 { Console.WriteLine("weekend albo blad"); }
switch
int dzien = 3;

switch (dzien)
{
    case 1:
        Console.WriteLine("poniedzialek");
        break;
    case 2:
        Console.WriteLine("wtorek");
        break;
    case 3:
        Console.WriteLine("sroda");
        break;
    case 4:
        Console.WriteLine("czwartek");
        break;
    case 5:
        Console.WriteLine("piatek");
        break;
    default:
        Console.WriteLine("weekend albo blad");
        break;
}
wynik dla dzien = 3
sroda

Co zyskujemy

Nazwa sprawdzanej zmiennej pojawia się raz, a nie w każdej linii. Dzięki temu nie da się przez pomyłkę porównać w jednej gałęzi innej zmiennej — a to zaskakująco częsty błąd w długich drabinkach. Widać też od razu, że wszystkie warunki dotyczą tej samej wartości.

Etykieta default

default odpowiada gałęzi else: wykona się, gdy żaden case nie pasował. Nie jest obowiązkowa — bez niej program po prostu nic nie zrobi — ale w programie, który dostaje dane od użytkownika, jej brak jest błędem. Ktoś zawsze wpisze coś, czego nie przewidziałeś.

Etykieta default nie musi stać na końcu (kompilator to dopuszcza), ale zawsze ją tam pisz — inaczej czytający musi jej szukać.

TEORIA

Dlaczego gałąź switch musi zakończyć przepływ sterowania

Każda gałąź switch musi się jednoznacznie kończyć. Najczęściej robi to break, który znaczy „wychodzimy ze switch„. Pominięcie go to błąd kompilacji:

Program.cs
switch (ocena)
{
    case 5:
        Console.WriteLine("Bardzo dobry");
        // BLAD CS0163: nie mozna "przelecec" z jednej etykiety do drugiej
    case 4:
        Console.WriteLine("Dobry");
        break;
}

C# jest tu bezpieczniejszy niż C, C++ i Java

W tamtych językach brak break jest legalny i powoduje przelecenie do następnej gałęzi — program wypisałby „Bardzo dobry” i „Dobry”. To klasyczne źródło błędów, bo pomyłka wygląda jak poprawny kod. C# takiego zapisu po prostu nie skompiluje. Jeśli w cudzym kodzie w innym języku widzisz case bez break, przyjrzyj mu się uważnie.

Świadome przejście do innej gałęzi: goto case

Gdy naprawdę chcesz, żeby jedna gałąź przeszła do drugiej, C# każe napisać to wprost:

Program.cs
switch (poziom)
{
    case "administrator":
        Console.WriteLine("Moze zmieniac ustawienia.");
        goto case "uzytkownik";      // administrator ma tez prawa uzytkownika
    case "uzytkownik":
        Console.WriteLine("Moze czytac dane.");
        break;
    default:
        Console.WriteLine("Brak uprawnien.");
        break;
}

Zapis jest poprawny, ale rzadko potrzebny — zwykle czytelniej jest powtórzyć jedną linię albo wywołać wspólną metodę. Wspominam o nim, żebyś go rozpoznał, a nie po to, żebyś go używał.

Inne zakończenia gałęzi

Gałąź może się też skończyć instrukcją return (gdy switch stoi w metodzie — blok o funkcjach) albo throw (przy zgłaszaniu błędu — blok o wyjątkach). W programach z tej lekcji zawsze będzie to break.

TEORIA

Etykiety zbiorcze

Kilka etykiet case może prowadzić do jednego bloku instrukcji. To nie jest „przelecenie” — pusta etykieta niczego nie wykonuje, więc jest bezpieczna i nie wymaga break:

Program.cs
int miesiac = 7;

switch (miesiac)
{
    case 12:
    case 1:
    case 2:
        Console.WriteLine("zima");
        break;

    case 3:
    case 4:
    case 5:
        Console.WriteLine("wiosna");
        break;

    case 6:
    case 7:
    case 8:
        Console.WriteLine("lato");
        break;

    case 9:
    case 10:
    case 11:
        Console.WriteLine("jesien");
        break;

    default:
        Console.WriteLine("Numer miesiaca musi byc z zakresu 1-12.");
        break;
}
wynik dla miesiac = 7
lato

Ten sam zapis drabinką else if wymagałby warunków w rodzaju miesiac == 12 || miesiac == 1 || miesiac == 2 — dłuższych i łatwiejszych do pomylenia.

Nowszy, krótszy zapis

Od C# 9 kilka wartości można wypisać w jednej etykiecie połączone słowem or: case 12 or 1 or 2:. Działa też zakres: case >= 6 and <= 8:. Wrócimy do tego w sekcji o wzorcach.

TEORIA

Na czym switch działa, a na czym nie

W prostym wzorcu stałej, np. case 5:, wartość musi być znana podczas kompilacji. Może być wyrażeniem stałym, np. 2 + 3. Współczesny switch obsługuje również inne wzorce i warunki when, które omówimy niżej.

TypUwagi
int, long, short, byteTypowe zastosowanie: numer opcji, kod błędu, numer miesiąca.
charLitera menu, znak operatora. Etykieta w apostrofach: case 'a':.
stringDziała i jest wygodne, ale rozróżnia wielkość liter — patrz ostrzeżenie niżej.
boolDopuszczalne, ale bez sensu — dwie możliwości to zadanie dla if/else.
enumNajlepsze połączenie. Typ wyliczeniowy poznamy w lekcji 49.
double, float, decimalTechnicznie dozwolone, praktycznie nigdy. Porównywanie liczb ułamkowych z konkretną wartością jest zawodne — po to jest if z tolerancją.
Program.cs
int prog = 5;

switch (ocena)
{
    case prog:      // BLAD CS0150: oczekiwano stalej
        break;
}

const int Prog = 5;

switch (ocena)
{
    case Prog:      // OK - stala jest znana przy kompilacji
        break;
}

switch na napisach rozróżnia wielkość liter

case "tak": nie złapie odpowiedzi „TAK” ani „Tak”. Sam wzorzec stałej nie przyjmuje StringComparison. Można użyć dodatkowego warunku when z porównaniem, ale na początek prostsze będzie ujednolicenie wpisu. Rozwiązanie: ujednolić wartość przed wejściem do switch.

Program.cs
string odp = (Console.ReadLine() ?? "").Trim().ToLower();

switch (odp)
{
    case "tak":
    case "t":
        Console.WriteLine("Potwierdzono.");
        break;
    case "nie":
    case "n":
        Console.WriteLine("Anulowano.");
        break;
    default:
        Console.WriteLine("Nie rozumiem odpowiedzi.");
        break;
}

Trim() usuwa spacje z początku i końca, ToLower() zamienia wszystko na małe litery. Po tych dwóch krokach „ TAK „, „Tak” i „tak” trafiają w tę samą etykietę.

TEORIA

Wyrażenie switch

Bardzo często cały switch służy tylko do przypisania jednej wartości. Od C# 8 jest na to krótszy zapis — wyrażenie switch. Tak jak operator ?: z poprzedniej lekcji, zwraca wartość:

instrukcja — 15 linii
string nazwa;

switch (ocena)
{
    case 6: nazwa = "celujacy"; break;
    case 5: nazwa = "bardzo dobry"; break;
    case 4: nazwa = "dobry"; break;
    case 3: nazwa = "dostateczny"; break;
    case 2: nazwa = "dopuszczajacy"; break;
    case 1: nazwa = "niedostateczny"; break;
    default: nazwa = "nieznana ocena"; break;
}
wyrażenie — 9 linii
string nazwa = ocena switch
{
    6 => "celujacy",
    5 => "bardzo dobry",
    4 => "dobry",
    3 => "dostateczny",
    2 => "dopuszczajacy",
    1 => "niedostateczny",
    _ => "nieznana ocena"
};
RóżnicaInstrukcja / wyrażenie
Słowo switchPrzed nawiasem / po nazwie zmiennej
Etykietycase 6: / samo 6 =>
Rozdzielenie gałęzibreak; / przecinek
Przypadek domyślnydefault: / znak podkreślenia _
ZakończenieKlamra / klamra i średnik
Co robiWykonuje instrukcje / zwraca wartość

Średnik po klamrze zamykającej

Wyrażenie switch jest częścią instrukcji przypisania, więc kończy się średnikiem: };. To najczęstsza pomyłka przy pierwszym użyciu — po instrukcji switch średnika nie ma.

Znak _ nie jest opcjonalny

W wyrażeniu switch gałąź _ jest praktycznie obowiązkowa. Bez niej kompilator ostrzega, że nie wszystkie wartości są obsłużone, a gdy program natrafi na nieprzewidziany przypadek, przerwie działanie z błędem SwitchExpressionException. W instrukcji switch brak default oznacza tylko „nic nie rób”.

Wyrażenie switch można wstawić wszędzie tam, gdzie potrzebna jest wartość — także wprost do Console.WriteLine:

Program.cs
int liczbaPlikow = 1;

string slowo = liczbaPlikow switch
{
    1 => "plik",
    2 or 3 or 4 => "pliki",
    _ => "plikow"
};

Console.WriteLine($"Znaleziono {liczbaPlikow} {slowo}");
wynik dla liczbaPlikow = 1
Znaleziono 1 plik

Polska odmiana jest trudniejsza, niż wygląda

Powyższy zapis działa dla liczb 0–21, ale już 22 wymaga formy „pliki”, a nie „plikow”. Pełna reguła zależy od dwóch ostatnich cyfr i sama nadaje się na zadanie ze wzorców. Zapamiętaj z tego przykładu jedno: wyrażenie switch można przypisać do zmiennej i użyć w interpolacji jak każdej innej wartości.

TEORIA

Wzorce: switch na zakresach

Nowsze wersje C# pozwalają w etykiecie napisać nie tylko konkretną wartość, ale i warunek. Dzięki temu switch radzi sobie z progami, które dotąd wymagały drabinki else if:

Program.cs
int procent = 82;

string ocena = procent switch
{
    < 0 or > 100 => "wynik poza zakresem",
    >= 98 => "6",
    >= 90 => "5",
    >= 75 => "4",
    >= 50 => "3",
    >= 30 => "2",
    _ => "1"
};

Console.WriteLine($"Wynik {procent}% to ocena {ocena}.");
wynik dla procent = 82
Wynik 82% to ocena 4.
WzorzecZnaczenie
>= 90wartość większa lub równa 90 — działają wszystkie operatory porównania
1 or 2 or 3jedna z wypisanych wartości; zastępuje etykiety zbiorcze
>= 6 and <= 8przedział — oba warunki naraz
not 0cokolwiek poza zerem
_wszystko pozostałe

Kolejność nadal decyduje

Wzorce są sprawdzane od góry, dokładnie jak gałęzie else if. Gdyby >= 50 stało przed >= 90, wynik 95% dostałby ocenę 3. Zaleta jest inna: zapis jest krótszy i widać w nim całą tabelę progów naraz.

Ten sam mechanizm działa też w zwykłej instrukcji switch — słowem kluczowym when:

Program.cs
switch (wiek)
{
    case int w when w < 0:
        Console.WriteLine("Wiek nie moze byc ujemny.");
        break;
    case 0:
        Console.WriteLine("Noworodek.");
        break;
    case int w when w < 18:
        Console.WriteLine("Osoba niepelnoletnia.");
        break;
    default:
        Console.WriteLine("Osoba pelnoletnia.");
        break;
}

To pełnoprawne rozwiązanie, ale w praktyce w takich sytuacjach częściej sięgamy po drabinkę else if albo po wyrażenie switch ze wzorcami — oba są krótsze.

TEORIA

Kiedy switch, a kiedy if

Wybierz switch, gdy…Wybierz if, gdy…
decyzja zależy od jednej wartości warunek łączy kilka zmiennych (wiek >= 18 && maPrawoJazdy)
możliwości jest co najmniej trzy możliwości są dwie
porównujesz z konkretnymi wartościami porównujesz liczby ułamkowe albo używasz tolerancji
lista opcji jest zamknięta (miesiące, menu, kody) warunek to złożone wyrażenie logiczne

W praktyce: jeśli piszesz else if po raz trzeci i za każdym razem porównujesz tę samą zmienną z inną wartością — to jest switch.

METODY WBUDOWANE

Użyte w tej lekcji metody

NazwaZwracaDziałanie
s.Trim()string Zwraca nowy napis bez spacji, tabulatorów i znaków końca linii z początku i końca. Środek zostaje nietknięty. Warianty: TrimStart(), TrimEnd().
s.ToLower() / s.ToUpper()string Zwraca napis zamieniony w całości na małe / wielkie litery. Oryginał zostaje bez zmian — napisy w C# są niezmienne.
Math.Abs(x)liczba Wartość bezwzględna. W kalkulatorze służy do sprawdzenia, czy dzielnik nie jest (prawie) zerem.
double.IsNaN(x)bool Czy wartość to „nie liczba” — wynik operacji bez sensu, np. 0.0 / 0.0 albo pierwiastek z liczby ujemnej.
Enum, DateTime.Now.DayOfWeek— Wyliczenia i typ DateTime dają wartości, na których switch sprawdza się najlepiej. Poznamy je w dalszych blokach kursu.
PRZYKŁAD Z OMÓWIENIEM

Kalkulator z menu

Klasyczne zadanie egzaminacyjne: program wczytuje dwie liczby i znak działania, po czym wypisuje wynik. To dokładnie ten kształt problemu, do którego switch został stworzony — jedna wartość, kilka z góry znanych możliwości.

Program.cs
Console.WriteLine("=== KALKULATOR ===");
Console.WriteLine();

Console.Write("Pierwsza liczba: ");
bool ok1 = double.TryParse(Console.ReadLine(), out double a);

Console.Write("Znak dzialania (+ - * /): ");
string wpis = (Console.ReadLine() ?? "").Trim();

Console.Write("Druga liczba: ");
bool ok2 = double.TryParse(Console.ReadLine(), out double b);

Console.WriteLine();

if (!ok1 || !ok2)
{
    Console.WriteLine("Obie wartosci musza byc liczbami.");
}
else if (wpis.Length != 1)
{
    Console.WriteLine("Podaj dokladnie jeden znak dzialania.");
}
else
{
    char dzialanie = wpis[0];
    double wynik = 0;
    bool policzono = true;

    switch (dzialanie)
    {
        case '+':
            wynik = a + b;
            break;

        case '-':
            wynik = a - b;
            break;

        case '*':
        case 'x':                      // wielu uzytkownikow pisze x zamiast gwiazdki
            wynik = a * b;
            break;

        case '/':
            if (Math.Abs(b) < 0.000001)
            {
                Console.WriteLine("Nie dziel przez zero.");
                policzono = false;
            }
            else
            {
                wynik = a / b;
            }
            break;

        default:
            Console.WriteLine($"Nieznane dzialanie: {dzialanie}");
            policzono = false;
            break;
    }

    if (policzono)
    {
        Console.WriteLine($"{a} {dzialanie} {b} = {wynik:F4}");
    }
}
wynik w konsoli
=== KALKULATOR ===

Pierwsza liczba: 7,5
Znak dzialania (+ - * /): /
Druga liczba: 2

7,5 / 2 = 3,7500

Omówienie

FragmentCo robi i dlaczego tak
wpis.Length != 1 Zanim wyciągniemy pierwszy znak, sprawdzamy, że jakiś w ogóle jest. Bez tego wpis[0] na pustym napisie przerwałby program błędem IndexOutOfRangeException.
char dzialanie = wpis[0]; Kwadratowe nawiasy dają pojedynczy znak napisu. switch na char jest wygodniejszy niż na string — etykiety zapisujemy w apostrofach.
double wynik = 0; Wartość początkowa jest konieczna: kompilator nie wie, że któraś gałąź switch na pewno przypisze wynik, więc bez niej zgłosiłby CS0165.
bool policzono = true; Flaga — zmienna logiczna zapamiętująca, czy coś się udało. Dwie gałęzie ustawiają ją na false, a wypisanie wyniku dzieje się po switch, tylko raz. Bez flagi trzeba by powtórzyć tę samą linię w każdej gałęzi.
case '*': case 'x': Etykiety zbiorcze. Pusta etykieta '*' niczego nie wykonuje, więc nie potrzebuje break — to nie jest zabronione „przelecenie”.
Math.Abs(b) < 0.000001 Sprawdzenie dzielnika. Nie piszemy b == 0, bo b jest typu double i po wcześniejszych obliczeniach mogłoby wynosić 0,0000000001 — technicznie nie zero, a dzielenie i tak dałoby bezużyteczny wynik.
if wewnątrz case Gałąź switch to zwykły blok instrukcji — może w nim stać if, obliczenia, wszystko. break musi być na końcu całej gałęzi, nie w każdej odnodze if.

Wersja krótsza — ale nie zawsze lepsza

Samo działanie da się zapisać wyrażeniem switch w czterech liniach. Tutaj byłoby to trudniejsze, bo dzielenie przez zero wymaga własnego komunikatu i pominięcia wypisania wyniku. Wyrażenie switch jest dla przypadków, w których każda gałąź daje wartość; gdy któraś ma zrobić coś innego, wracamy do instrukcji.

CZĘSTE BŁĘDY

Na czym się najczęściej potykamy

BłądCo się dzieje i jak poprawić
brak break CS0163: Control cannot fall through from one case label to another. Każda gałąź z instrukcjami musi kończyć się break (albo return, throw, goto case).
zmienna jako etykieta CS0150: A constant value is expected. Etykieta musi być literałem albo stałą const. Jeśli potrzebujesz zmiennej — to zadanie dla if.
brak średnika po wyrażeniu switch CS1002. Wyrażenie kończy się };, instrukcja — samą klamrą.
case "tak" na odpowiedzi „TAK” Nie zadziała — switch na napisach rozróżnia wielkość liter i nie przyjmuje StringComparison. Ujednolić przed: .Trim().ToLower().
brak default W instrukcji: program po cichu nic nie zrobi i nie dowiesz się dlaczego. W wyrażeniu: ostrzeżenie przy kompilacji i wyjątek SwitchExpressionException przy nieprzewidzianej wartości.
deklaracja zmiennej w gałęzi Cały switch to jeden zasięg — nie można zadeklarować zmiennej o tej samej nazwie w dwóch gałęziach (CS0128). Zadeklaruj ją przed switch albo obejmij gałąź własnymi klamrami.
dwie identyczne etykiety CS0152 — ta sama wartość nie może pojawić się w dwóch case. Przy dużej liczbie gałęzi łatwo o taką pomyłkę i dobrze, że kompilator ją łapie.
switch na kilku zmiennych switch patrzy na jedną wartość. Warunek typu „wiek i posiadanie prawa jazdy” zapisz instrukcją if.
zła kolejność wzorców Przy etykietach z porównaniami (>= 90) obowiązuje ta sama reguła co w drabince: od najostrzejszego warunku. Kompilator ostrzeże tylko wtedy, gdy któraś gałąź jest całkiem nieosiągalna.
ZADANIA

Zadania

ZAD 1Nazwa dnia tygodnia★☆☆

Wczytaj numer dnia tygodnia (1–7) i wypisz jego nazwę. Dla 6 i 7 dopisz „ (weekend)”. Numery spoza zakresu odrzuć.

ZAD 2Liczba dni w miesiącu★☆☆

Wczytaj numer miesiąca i rok, po czym wypisz liczbę dni w tym miesiącu. Luty ma 28 dni, a w roku przestępnym 29. Wykorzystaj etykiety zbiorcze.

ZAD 3Menu programu★★☆

Wyświetl menu z czterema opcjami (dodaj, usuń, wypisz, koniec), wczytaj wybór jako literę i wypisz komunikat o wybranej operacji. Program ma przyjmować zarówno małe, jak i wielkie litery.

ZAD 4Wyrażenie switch: znak zodiaku i pora roku★★☆

Napisz program, który dla numeru miesiąca zwraca porę roku — wyrażeniem switch, w jednej instrukcji przypisania. Użyj słowa or zamiast osobnych etykiet.

ZAD 5Ocena słownie i wzorce★★☆

Wczytaj ocenę (1–6) i wypisz jej nazwę słowną oraz informację, czy jest pozytywna. Nazwę ustal wyrażeniem switch na wartościach, a ocenę pozytywną — wyrażeniem switch ze wzorcem porównania.

ZAD 6Przelicznik jednostek★★★

Program wczytuje wartość oraz jednostkę źródłową i docelową (km, m, cm, mm). Przelicz wartość między jednostkami. Wskazówka: zamiast pisać wszystkie 16 kombinacji, przelicz najpierw na metry, a potem z metrów na jednostkę docelową.

PODSUMOWANIE

Co trzeba zapamiętać

  • switch wybiera gałąź przez dopasowanie wzorca do wartości. Proste case używają stałych.
  • Gałąź z wykonywalnym kodem musi zakończyć przepływ, np. break, return lub throw; break nie jest jedyną możliwością.
  • Kilka etykiet może wskazywać jeden wspólny blok.
  • Wzorce relacyjne i logiczne, np. >= 90 lub 1 or 2, rozszerzają proste porównanie ze stałą.
  • Wyrażenie switch zwraca wartość. Warto zapewnić obsługę pozostałych przypadków przez _.
  • Stała tekstowa rozróżnia wielkość liter. Warunek when może wykonać bardziej złożone sprawdzenie.
  • Dobieraj switch albo if według czytelności, a nie sztywnej liczby zmiennych.
Postęp zapisuje się w tej przeglądarce.