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 break jest obowiązkowy

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 przecinku: 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

Etykieta case musi być stałą — wartością znaną w chwili kompilacji. Nie może to być zmienna ani wynik obliczenia.

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 bloku o C# nowoczesnym.
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”. W przeciwieństwie do if nie da się tu podać StringComparison. 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ć.

pokaż rozwiązanie
Console.Write("Numer dnia (1-7): ");
bool ok = int.TryParse(Console.ReadLine(), out int dzien);

if (!ok)
{
    Console.WriteLine("Podaj liczbe.");
}
else
{
    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;
        case 6: Console.WriteLine("sobota (weekend)"); break;
        case 7: Console.WriteLine("niedziela (weekend)"); break;
        default: Console.WriteLine("Numer musi byc z zakresu 1-7."); break;
    }
}
wynik w konsoli
Numer dnia (1-7): 6
sobota (weekend)

Przy jednolinijkowych gałęziach cały case mieści się w jednej linii — czyta się to jak tabelę. Sprawdzenie TryParse zostaje na zewnątrz, bo switch nie ma jak obsłużyć „to w ogóle nie była liczba”.

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.

pokaż rozwiązanie
Console.Write("Numer miesiaca (1-12): ");
bool okM = int.TryParse(Console.ReadLine(), out int miesiac);

Console.Write("Rok: ");
bool okR = int.TryParse(Console.ReadLine(), out int rok);

if (!okM || !okR || rok < 1)
{
    Console.WriteLine("Podaj poprawny miesiac i rok.");
}
else
{
    int dni;

    switch (miesiac)
    {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            dni = 31;
            break;

        case 4:
        case 6:
        case 9:
        case 11:
            dni = 30;
            break;

        case 2:
            dni = DateTime.IsLeapYear(rok) ? 29 : 28;
            break;

        default:
            dni = 0;
            break;
    }

    if (dni == 0)
    {
        Console.WriteLine("Numer miesiaca musi byc z zakresu 1-12.");
    }
    else
    {
        Console.WriteLine($"Miesiac {miesiac} w roku {rok} ma {dni} dni.");
    }
}
wynik w konsoli
Numer miesiaca (1-12): 2
Rok: 2024
Miesiac 2 w roku 2024 ma 29 dni.

Etykiety zbiorcze zastępują tu warunek z sześcioma operatorami ||. Zwróć uwagę, że gałąź lutego zawiera operator warunkowy — gałąź switch może robić dowolne obliczenia.

Wartość 0 pełni rolę znacznika „nie ustalono”. W prawdziwym programie lepszym rozwiązaniem byłaby osobna flaga albo od razu wypisanie komunikatu w gałęzi default — tu zostawiam ten zapis, bo pokazuje typową konstrukcję, którą spotkasz w cudzym kodzie.

Istnieje też gotowa metoda: DateTime.DaysInMonth(rok, miesiac). W programie użytkowym należałoby jej użyć — to zadanie jest ćwiczeniem ze switch.

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.

pokaż rozwiązanie
Console.WriteLine("=== MENU ===");
Console.WriteLine("  d - dodaj pozycje");
Console.WriteLine("  u - usun pozycje");
Console.WriteLine("  w - wypisz wszystkie");
Console.WriteLine("  k - koniec");
Console.WriteLine();

Console.Write("Twoj wybor: ");
string wpis = (Console.ReadLine() ?? "").Trim().ToLower();

if (wpis.Length == 0)
{
    Console.WriteLine("Nic nie wybrano.");
}
else
{
    switch (wpis[0])
    {
        case 'd':
            Console.WriteLine("Dodawanie nowej pozycji...");
            break;
        case 'u':
            Console.WriteLine("Usuwanie pozycji...");
            break;
        case 'w':
            Console.WriteLine("Lista pozycji:");
            break;
        case 'k':
            Console.WriteLine("Do zobaczenia!");
            break;
        default:
            Console.WriteLine($"Nieznana opcja: '{wpis[0]}'");
            break;
    }
}
wynik w konsoli
=== MENU ===
  d - dodaj pozycje
  u - usun pozycje
  w - wypisz wszystkie
  k - koniec

Twoj wybor: W
Lista pozycji:

ToLower() wywołane przed switch załatwia obsługę wielkich liter raz dla wszystkich gałęzi. Alternatywa — dopisanie case 'D': obok każdego case 'd': — podwoiłaby liczbę etykiet.

Kolejność Trim(), potem ToLower() nie ma tu znaczenia, ale warto wyrobić sobie nawyk: najpierw obcinamy śmieci, potem ujednolicamy.

Prawdziwe menu powtarza się do wyboru opcji „koniec”. Dopiszemy tę pętlę w następnej lekcji.

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.

pokaż rozwiązanie
Console.Write("Numer miesiaca (1-12): ");
bool ok = int.TryParse(Console.ReadLine(), out int miesiac);

string pora = miesiac switch
{
    12 or 1 or 2 => "zima",
    3 or 4 or 5  => "wiosna",
    6 or 7 or 8  => "lato",
    9 or 10 or 11 => "jesien",
    _ => "nieznany miesiac"
};

Console.WriteLine(ok ? $"Miesiac {miesiac} to {pora}." : "Podaj liczbe.");
wynik w konsoli
Numer miesiaca (1-12): 11
Miesiac 11 to jesien.

Piętnaście linii instrukcji switch zmieściło się w siedmiu. Gałąź _ obsługuje zarówno numery spoza zakresu, jak i zero, które TryParse zostawia po nieudanej konwersji.

Zwróć uwagę na średnik po klamrze zamykającej — to część instrukcji przypisania.

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.

pokaż rozwiązanie
Console.Write("Ocena (1-6): ");
bool ok = int.TryParse(Console.ReadLine(), out int ocena);

if (!ok)
{
    Console.WriteLine("Podaj liczbe calkowita.");
}
else
{
    string nazwa = ocena switch
    {
        6 => "celujacy",
        5 => "bardzo dobry",
        4 => "dobry",
        3 => "dostateczny",
        2 => "dopuszczajacy",
        1 => "niedostateczny",
        _ => "ocena spoza skali"
    };

    string status = ocena switch
    {
        >= 2 and <= 6 => "pozytywna",
        1 => "negatywna",
        _ => "nieokreslona"
    };

    Console.WriteLine($"{ocena} - {nazwa} ({status})");
}
wynik w konsoli
Ocena (1-6): 4
4 - dobry (pozytywna)

Wzorzec >= 2 and <= 6 zamyka przedział z obu stron — bez górnej granicy ocena 9 zostałaby uznana za pozytywną. To ta sama pułapka co w drabince else if, tylko zapisana krócej.

Dwa niezależne wyrażenia switch na tej samej zmiennej to nie marnotrawstwo: każde odpowiada na inne pytanie i każde da się zmienić osobno.

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

pokaż rozwiązanie
Console.Write("Wartosc: ");
bool ok = double.TryParse(Console.ReadLine(), out double wartosc);

Console.Write("Jednostka zrodlowa (km/m/cm/mm): ");
string zJednostki = (Console.ReadLine() ?? "").Trim().ToLower();

Console.Write("Jednostka docelowa (km/m/cm/mm): ");
string naJednostke = (Console.ReadLine() ?? "").Trim().ToLower();

// ile metrow ma jedna jednostka; 0 oznacza "nie znam takiej"
double wspZrodla = zJednostki switch
{
    "km" => 1000.0,
    "m"  => 1.0,
    "cm" => 0.01,
    "mm" => 0.001,
    _    => 0.0
};

double wspCelu = naJednostke switch
{
    "km" => 1000.0,
    "m"  => 1.0,
    "cm" => 0.01,
    "mm" => 0.001,
    _    => 0.0
};

Console.WriteLine();

if (!ok)
{
    Console.WriteLine("Wartosc musi byc liczba.");
}
else if (wspZrodla == 0 || wspCelu == 0)
{
    Console.WriteLine("Obslugiwane jednostki to: km, m, cm, mm.");
}
else
{
    double wMetrach = wartosc * wspZrodla;
    double wynik = wMetrach / wspCelu;

    Console.WriteLine($"{wartosc} {zJednostki} = {wynik} {naJednostke}");
}
wynik w konsoli
Wartosc: 2,5
Jednostka zrodlowa (km/m/cm/mm): km
Jednostka docelowa (km/m/cm/mm): cm

2,5 km = 250000 cm

To jest najważniejszy pomysł w tym zadaniu. Zamiast szesnastu gałęzi (każda jednostka z każdą) mamy dwie listy po cztery pozycje: „ile metrów ma jedna sztuka tej jednostki”. Przeliczenie sprowadza się do mnożenia i dzielenia. Gdy dojdzie piąta jednostka, dopisujesz dwie linie zamiast dziewięciu.

Wartość 0 w gałęzi _ pełni rolę znacznika „nieznana jednostka” — bezpiecznie, bo żadna prawdziwa jednostka nie ma współczynnika zero. Sprawdzamy go przed dzieleniem.

Oba wyrażenia switch liczą się zawsze, nawet gdy dane były błędne. To bez znaczenia — nic nie kosztują i nie mogą się wywalić. Sprawdzenie poprawności jest tam, gdzie trzeba: przed użyciem wyników.

Sprawdź sam: 500 cm na metry da 5, a 1500 m na km da 1,5.

PODSUMOWANIE

Co trzeba zapamiętać

  • switch porównuje jedną wartość z listą stałych — nazwa zmiennej pojawia się tylko raz.
  • W C# break jest obowiązkowy; brak przejścia między gałęziami to błąd CS0163, a nie ciche „przelecenie” jak w C czy Javie.
  • Kilka etykiet case nad jednym blokiem to etykiety zbiorcze — one break nie potrzebują.
  • Etykieta musi być stałą; zmienna daje CS0150.
  • switch na napisach rozróżnia wielkość liter — ujednolicaj wcześniej: .Trim().ToLower().
  • Wyrażenie switch (zmienna switch { 1 => ..., _ => ... };) zwraca wartość i kończy się średnikiem.
  • Wzorce (>= 90, 1 or 2, >= 6 and <= 8) pozwalają obsłużyć progi i zakresy; kolejność wciąż decyduje.
  • default (lub _) pisz zawsze — użytkownik wpisze coś, czego nie przewidziałeś.
  • Warunek na kilku zmiennych to zadanie dla if, nie dla switch.