Instrukcja warunkowa if / else
Do tej pory twoje programy wykonywały wszystkie linie po kolei, od pierwszej do ostatniej — zawsze tak samo. Od tej lekcji program zaczyna podejmować decyzje: jedne linie wykona, inne pominie, w zależności od tego, jakie dane dostał.
Czego się dziś nauczysz
- Wykonasz fragment kodu tylko wtedy, gdy warunek jest spełniony
- Zbudujesz drabinkę
else ifi ustawisz jej warunki we właściwej kolejności - Wyjaśnisz, dlaczego w C# nie da się napisać
if (x = 5) - Skrócisz proste rozgałęzienie operatorem
?: - Zrozumiesz, dlaczego zmienna zadeklarowana w bloku znika po jego zamknięciu
Instrukcja if
Instrukcja if mówi: „jeśli warunek jest prawdziwy, wykonaj to, co w klamrach; jeśli nie — pomiń”.
if (warunek)
{
// instrukcje wykonywane, gdy warunek jest prawdziwy
}
int wiek = 20;
Console.WriteLine("Sprawdzam wiek...");
if (wiek >= 18)
{
Console.WriteLine("Jestes pelnoletni.");
}
Console.WriteLine("Koniec sprawdzania.");
Sprawdzam wiek...
Jestes pelnoletni.
Koniec sprawdzania.
Sprawdzam wiek...
Koniec sprawdzania.
Zwróć uwagę, że ostatnia linia wykonała się w obu przypadkach — jest poza klamrami, więc if jej nie dotyczy.
Po nawiasie warunku nie ma średnika
Zapis if (wiek >= 18); to pusta instrukcja: „jeśli pełnoletni, nie rób nic”. Blok w klamrach wykona się wtedy zawsze. Kompilator ostrzeże (CS0642), ale programu nie zatrzyma, więc łatwo to przeoczyć.
Klamry przy jednej instrukcji
Gdy w środku jest tylko jedna instrukcja, klamry można pominąć. Ale nie warto. Porównaj:
// dziala, ale kruche
if (saldo < 0)
Console.WriteLine("Debet!");
// ktos dopisuje druga linie i program przestaje dzialac poprawnie
if (saldo < 0)
Console.WriteLine("Debet!");
Console.WriteLine("Zablokowano karte."); // wykona sie ZAWSZE
Wcięcie sugeruje, że obie linie należą do if — ale kompilator wcięć nie czyta. Bez klamer do if należy dokładnie jedna instrukcja, ta pierwsza. W tym kursie klamry stawiamy zawsze.
Warunek musi być typu bool
W nawiasie if może stać wyłącznie wyrażenie typu bool — czyli takie, które daje true albo false. Nie liczba, nie napis. To jedna z ważniejszych różnic między C# a językami C i C++.
int x = 5;
if (x) // BLAD CS0029: nie mozna zamienic int na bool
{
}
if (x = 5) // BLAD CS0029: to przypisanie, wynikiem jest liczba 5
{
}
if (x == 5) // OK: porownanie, wynikiem jest true
{
}
Kompilator C# ratuje cię przed klasycznym błędem
W języku C zapis if (x = 5) jest poprawny — przypisuje 5 i uznaje wynik za prawdę, więc warunek zawsze jest spełniony. To źródło setek tysięcy błędów. W C# taki zapis po prostu się nie skompiluje, bo liczba nie jest wartością logiczną. Jeden znak = zamiast dwóch zostanie natychmiast wychwycony.
Zmienna logiczna jako warunek
Skoro warunek to bool, to zmienną typu bool można wstawić wprost — bez porównywania z true:
bool ok = int.TryParse("42", out int liczba);
if (ok) // dobrze
{
Console.WriteLine(liczba);
}
if (ok == true) // dziala, ale to maslo maslane
{
}
if (!ok) // zaprzeczenie: "jesli sie NIE udalo"
{
Console.WriteLine("Blad danych.");
}
Nazwa zmiennej logicznej powinna brzmieć jak zdanie oznajmujące, żeby warunek dało się przeczytać po polsku: if (czyPelnoletni), if (znaleziono), if (!pusty).
else i else if
else to „w przeciwnym razie”. Blok else wykona się dokładnie wtedy, gdy warunek if był fałszywy — nie trzeba pisać warunku odwrotnego.
int wiek = 15;
if (wiek >= 18)
{
Console.WriteLine("Pelnoletni");
}
else
{
Console.WriteLine("Niepelnoletni");
}
Zawsze wykona się dokładnie jedna z dwóch gałęzi — nigdy obie i nigdy żadna.
Więcej niż dwie możliwości: else if
int temperatura = 12;
if (temperatura < 0)
{
Console.WriteLine("Mroz");
}
else if (temperatura < 10)
{
Console.WriteLine("Zimno");
}
else if (temperatura < 20)
{
Console.WriteLine("Chlodno");
}
else
{
Console.WriteLine("Cieplo");
}
Chlodno
Drabinka kończy się na pierwszym trafieniu
Program sprawdza warunki po kolei od góry i zatrzymuje się na pierwszym prawdziwym. Dla 12 stopni: 12 < 0 to fałsz, 12 < 10 to fałsz, 12 < 20 to prawda → wypisuje „Chlodno” i nie sprawdza już reszty. Dlatego nie trzeba pisać else if (temperatura >= 10 && temperatura < 20) — wiadomo już, że poprzednie warunki były fałszywe.
Ostatni else jest opcjonalny, ale warto go mieć: łapie wszystkie przypadki, o których nie pomyślałeś. Bez niego, gdy żaden warunek nie pasuje, program po prostu nic nie zrobi — i nie dowiesz się dlaczego.
Kolejność warunków ma znaczenie
To najczęstszy błąd logiczny początkujących — kod się kompiluje, uruchamia i daje złe wyniki. Spójrz na dwie wersje tego samego przelicznika procentów na ocenę:
int procent = 95;
if (procent >= 50) // 95 >= 50 to PRAWDA, drabinka sie konczy
{
Console.WriteLine("3");
}
else if (procent >= 75)
{
Console.WriteLine("4");
}
else if (procent >= 90)
{
Console.WriteLine("5");
}
3
int procent = 95;
if (procent >= 90)
{
Console.WriteLine("5");
}
else if (procent >= 75)
{
Console.WriteLine("4");
}
else if (procent >= 50)
{
Console.WriteLine("3");
}
else
{
Console.WriteLine("2");
}
5
Reguła: od najostrzejszego warunku do najłagodniejszego
Przy progach z >= zaczynamy od największej wartości i schodzimy w dół. Przy progach z <= — odwrotnie, od najmniejszej w górę. Jeśli warunki są rozłączne (np. porównania z różnymi napisami), kolejność nie ma znaczenia.
Zagnieżdżanie a &&
Wewnątrz if może stać kolejny if. Czasem to konieczne, ale bardzo często da się to zapisać prościej — operatorem && z lekcji 10:
// zagniezdzenie
if (wiek >= 18)
{
if (maPrawoJazdy)
{
Console.WriteLine("Moze prowadzic.");
}
}
// to samo, czytelniej
if (wiek >= 18 && maPrawoJazdy)
{
Console.WriteLine("Moze prowadzic.");
}
Zagnieżdżenie jest potrzebne dopiero wtedy, gdy wewnętrzny warunek ma własne else:
if (wiek >= 18)
{
if (maPrawoJazdy)
{
Console.WriteLine("Moze prowadzic.");
}
else
{
Console.WriteLine("Pelnoletni, ale bez uprawnien.");
}
}
else
{
Console.WriteLine("Za mlody.");
}
Trzy poziomy zagnieżdżenia to sygnał ostrzegawczy
Kod wcięty na cztery poziomy jest prawie nie do przeczytania. Zwykle da się go spłaszczyć: połączyć warunki przez &&, odwrócić warunek i wyjść wcześniej, albo — od bloku o funkcjach — wydzielić fragment do osobnej metody.
Zmienne zadeklarowane w bloku
Klamry wyznaczają zasięg. Zmienna zadeklarowana wewnątrz bloku przestaje istnieć po klamrze zamykającej:
if (procent >= 50)
{
string wynik = "zdany";
}
Console.WriteLine(wynik); // BLAD CS0103: nazwa 'wynik' nie istnieje
Poprawka: deklarujemy zmienną przed instrukcją warunkową, a w blokach tylko przypisujemy wartość.
string wynik;
if (procent >= 50)
{
wynik = "zdany";
}
else
{
wynik = "niezdany";
}
Console.WriteLine(wynik); // OK
Każda ścieżka musi coś przypisać
Gdyby zabrakło gałęzi else, kompilator zgłosiłby CS0165: Use of unassigned local variable 'wynik'. To nie jest przesadna ostrożność — przy fałszywym warunku zmienna naprawdę nie miałaby żadnej wartości. Albo dopisz else, albo nadaj wartość początkową przy deklaracji: string wynik = "niezdany";.
Operator warunkowy ?:
Gdy całe rozgałęzienie sprowadza się do wybrania jednej z dwóch wartości, sześć linii z if/else można zastąpić jedną. Operator warunkowy (nazywany też trójargumentowym, bo jako jedyny w C# przyjmuje trzy argumenty) czyta się jak pytanie:
warunek ? wartosc_gdy_prawda : wartosc_gdy_falsz
int wiek = 15;
// wersja z if
string opis1;
if (wiek >= 18)
{
opis1 = "pelnoletni";
}
else
{
opis1 = "niepelnoletni";
}
// dokladnie to samo, jedna linia
string opis2 = wiek >= 18 ? "pelnoletni" : "niepelnoletni";
Console.WriteLine(opis2);
Operator warunkowy zwraca wartość, więc można go wstawić wszędzie tam, gdzie wartość jest potrzebna — także wprost w interpolacji:
int sztuki = 1;
double cena = 24.99;
double rabat = sztuki >= 10 ? 0.15 : 0.0;
Console.WriteLine($"Kupiles {sztuki} {(sztuki == 1 ? "sztuke" : "sztuk")}.");
Console.WriteLine($"Rabat: {rabat:P0}");
Kupiles 1 sztuke.
Rabat: 0 %
Nawias wewnątrz interpolacji
W zapisie {(sztuki == 1 ? "sztuke" : "sztuk")} nawias jest obowiązkowy. Bez niego dwukropek zostałby uznany za początek specyfikatora formatu z poprzedniej lekcji i kompilator zgłosiłby błąd.
Nie zagnieżdżaj operatora warunkowego
Zapis a ? "x" : b ? "y" : "z" jest poprawny, ale nieczytelny — a przy trzech poziomach nie da się go już zrozumieć bez kartki. Do wyboru z wielu możliwości służy drabinka else if albo instrukcja switch z następnej lekcji.
Krewny: operator ??
Znasz go już z lekcji 12. To skrót dla jednego konkretnego pytania: „czy wartość istnieje?”.
string? wpis = Console.ReadLine();
string imie = wpis ?? "Nieznajomy"; // gdy nic nie ma, uzyj wartosci zapasowej
// to samo dluzej
string imie2 = wpis != null ? wpis : "Nieznajomy";
Metody przydatne w warunkach
| Nazwa | Zwraca | Działanie |
|---|---|---|
string.IsNullOrEmpty(s) | bool |
true, gdy napis nie istnieje albo ma zero znaków. Zastępuje zapis s == null || s.Length == 0. |
string.IsNullOrWhiteSpace(s) | bool |
To samo, ale za pusty uznaje też napis złożony z samych spacji i tabulatorów. Do sprawdzania danych od użytkownika lepszy niż poprzedni — ktoś, kto naciśnie spację i Enter, nie wpisał imienia. |
s.StartsWith(t) / s.EndsWith(t) | bool |
Czy napis zaczyna się / kończy podanym fragmentem. Rozróżnia wielkość liter, chyba że podasz StringComparison.OrdinalIgnoreCase. |
s.Contains(t) | bool |
Czy napis zawiera podany fragment gdziekolwiek w środku. |
string.Equals(a, b, porownanie) | bool |
Porównanie napisów z jawną regułą. StringComparison.OrdinalIgnoreCase ignoruje wielkość liter — właściwy sposób na sprawdzenie odpowiedzi „tak”/„TAK”/„Tak”. |
char.IsDigit(c) | bool |
Czy znak jest cyfrą. Rodzeństwo: char.IsLetter, char.IsUpper, char.IsWhiteSpace. |
Math.Abs(x) | liczba | Wartość bezwzględna. Potrzebna przy porównywaniu liczb ułamkowych: Math.Abs(a - b) < 0.0001 zamiast a == b. |
Math.Max(a, b) / Math.Min(a, b) | liczba | Większa / mniejsza z dwóch wartości. Często zastępuje całe if: int wiekszy = Math.Max(a, b);. |
Math.Sqrt(x) | double |
Pierwiastek kwadratowy. Dla liczby ujemnej zwraca NaN („nie liczba”) — nie zgłasza błędu, więc znak trzeba sprawdzić samemu. |
Console.Write("Imie: ");
string imie = Console.ReadLine() ?? "";
if (string.IsNullOrWhiteSpace(imie))
{
Console.WriteLine("Nie podales imienia.");
}
else
{
Console.WriteLine($"Czesc, {imie}!");
}
Wynik szkolnego turnieju
Reguły ćwiczenia: 90–100 punktów daje finał, 50–89 dobry wynik, a 0–49 oznacza dalszy trening. Wartości spoza 0–100 są błędne.
Console.Write("Punkty w turnieju (0-100): ");
bool poprawnyZapis = int.TryParse(Console.ReadLine(), out int punkty);
if (!poprawnyZapis)
{
Console.WriteLine("Wpisz liczbe calkowita.");
}
else if (punkty < 0 || punkty > 100)
{
Console.WriteLine("Punkty musza byc w zakresie 0-100.");
}
else
{
string kategoria;
if (punkty >= 90) kategoria = "Finalista";
else if (punkty >= 50) kategoria = "Dobry wynik";
else kategoria = "Czas na trening";
Console.WriteLine($"Kategoria: {kategoria}");
}Punkty w turnieju (0-100): 90
Kategoria: FinalistaDlaczego warunki są w tej kolejności?
Najpierw sprawdzamy poprawność zapisu, potem sens liczby. Wewnątrz poprawnego przypadku progi z >= ustawiamy malejąco. Gdyby pierwszym warunkiem było punkty >= 50, uczestnik z 95 punktami trafiłby do niewłaściwej kategorii. Ostatni else przypisuje kategorię na każdej pozostałej ścieżce.
Użyte metody
Console.Write zwraca void i wypisuje zachętę bez nowej linii. Console.ReadLine zwraca string? — tekst lub null. int.TryParse zwraca bool oraz zapisuje przez out liczbę albo 0 przy niepowodzeniu. Console.WriteLine zwraca void i wypisuje kategorię w nowym wierszu. Interpolacja $”…{kategoria}…” jest składnią języka do wstawiania wartości w tekst.
Testy granic: 49 → trening, 50 → dobry wynik, 89 → dobry wynik, 90 → finał, 100 → finał. Dodatkowo -1, 101 oraz kot muszą być odrzucone. Te testy sprawdzają przejścia między kategoriami.
Na czym się najczęściej potykamy
| Błąd | Co się dzieje i jak poprawić |
|---|---|
if (x = 5) |
CS0029 — jeden znak = to przypisanie. Porównanie to ==. Na szczęście C# nie pozwoli tego uruchomić. |
if (warunek); |
Średnik kończy instrukcję if. Blok w klamrach wykona się zawsze, niezależnie od warunku. Kompilator ostrzega (CS0642), ale program się zbuduje. |
if (5 < x < 10) |
Zapis matematyczny, którego C# nie rozumie. Najpierw policzyłby 5 < x (wynik bool), a potem próbował porównać prawdę z liczbą — CS0019. Poprawnie: if (x > 5 && x < 10). |
| zła kolejność progów | Kompiluje się i uruchamia, ale zawsze wpada w pierwszy, najłagodniejszy warunek. Przy >= zaczynaj od największej wartości. |
if (0.1 + 0.2 == 0.3) |
Fałsz — liczby double są przybliżeniami. Porównuj różnicę: if (Math.Abs(a - b) < 0.0001). Do pieniędzy używaj decimal. |
| brak klamer przy dwóch instrukcjach | Do if bez klamer należy tylko pierwsza instrukcja. Druga wykona się zawsze, choć wcięcie sugeruje co innego. |
| zmienna zadeklarowana w bloku | CS0103 po klamrze zamykającej. Deklaruj przed if, w blokach tylko przypisuj. |
brak else przy przypisaniu |
CS0165: Use of unassigned local variable. Dopisz else albo nadaj wartość początkową przy deklaracji. |
if (odpowiedz == "tak") na odpowiedzi „TAK” |
Porównanie napisów rozróżnia wielkość liter, więc „TAK” nie przejdzie. Użyj string.Equals(odpowiedz, "tak", StringComparison.OrdinalIgnoreCase). |
else if zamiast osobnych if |
Odwrotna pułapka: gdy warunki nie wykluczają się nawzajem i każdy ma zadziałać niezależnie, drabinka jest błędem — po pierwszym trafieniu reszta zostanie pominięta. Wtedy pisz osobne, niepowiązane instrukcje if. |
Zadania
Wczytaj liczbę całkowitą i wypisz, czy jest parzysta, nieparzysta, czy równa zeru. Zero potraktuj jako osobny przypadek.
Wczytaj trzy liczby i wypisz największą. Rozwiąż zadanie na dwa sposoby: instrukcjami if oraz metodą Math.Max.
Wczytaj wynik procentowy (0–100) i wypisz ocenę według progów: 98 i więcej → 6, od 90 → 5, od 75 → 4, od 50 → 3, od 30 → 2, poniżej → 1. Odrzuć wartości spoza zakresu.
Wczytaj rok i sprawdź, czy jest przestępny. Reguła: rok podzielny przez 4 jest przestępny, chyba że jest podzielny przez 100 — wtedy musi być jeszcze podzielny przez 400.
Bilet normalny kosztuje 25 zł. Osobom do 18. roku życia oraz od 65. przysługuje bilet ulgowy za 18 zł. We wtorki obowiązuje dodatkowo 20% rabatu od wyliczonej ceny. Wczytaj wiek i odpowiedź na pytanie o wtorek, po czym wypisz cenę.
Wczytaj współczynniki a, b, c i rozwiąż równanie ax² + bx + c = 0. Uwzględnij trzy przypadki delty oraz sytuację, gdy a wynosi zero (równanie nie jest wtedy kwadratowe).
Co trzeba zapamiętać
- if wykonuje blok, gdy warunek bool jest prawdziwy.
- W drabince else if wykonuje się pierwsza pasująca gałąź.
- Dla progów >= sprawdzaj zwykle większe wartości najpierw; dla < mniejsze. Zawsze sprawdź granice.
- Osobne if służą niezależnym sprawdzeniom.
- Średnik bezpośrednio po if kończy pustą instrukcję i zwykle oznacza pomyłkę.
- Zmienna lokalna wewnątrz bloku nie jest dostępna z zewnątrz tego bloku.
- ?: wybiera jedną z dwóch wartości; nie trzeba nim zastępować każdej instrukcji if.
- Przed użyciem wyniku parsowania sprawdź bool i reguły zakresu.