Wczytywanie danych od użytkownika

Do tej pory wszystkie dane były wpisane na sztywno w kodzie. Teraz program zacznie pytać i słuchać. Poznasz metody konsoli do czytania z klawiatury i — co ważniejsze — nauczysz się pisać kod, który nie wywraca się, gdy ktoś wpisze coś innego, niż się spodziewałeś.

C# ReadLine walidacja 40 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wczytasz tekst z klawiatury metodą Console.ReadLine
  • Napiszesz czytelną zachętę, która mówi użytkownikowi, czego oczekujesz
  • Zamienisz wczytany tekst na liczbę bezpiecznym sposobem
  • Sprawdzisz, czy wprowadzona wartość mieści się w dozwolonym zakresie
  • Wyjaśnisz, dlaczego Console.ReadLine podkreśla się na żółto
TEORIA

Metoda Console.ReadLine

Ta jedna metoda robi wszystko, czego potrzeba do wczytania danych: zatrzymuje program, czeka na wpisanie tekstu i naciśnięcie klawisza Enter, po czym zwraca wpisany tekst.

Program.cs
Console.Write("Jak masz na imie? ");
string imie = Console.ReadLine();

Console.WriteLine($"Milo cie poznac, {imie}!");
wynik w konsoli (podkreślone = wpisane przez użytkownika)
Jak masz na imie? Anna
Milo cie poznac, Anna!

ReadLine zawsze zwraca tekst

Nawet jeśli użytkownik wpisze 42, dostaniesz napis "42", a nie liczbę. Żeby móc liczyć, trzeba wykonać konwersję z poprzedniej lekcji. To najczęstsze potknięcie przy pierwszych programach interaktywnych.

Żółte podkreślenie pod ReadLine

Visual Studio prawdopodobnie podkreśli ci tę linię i pokaże ostrzeżenie CS8600. Program działa, ale warto wiedzieć, o co chodzi.

Program.cs
string imie = Console.ReadLine();
//            └── ostrzeżenie CS8600: konwersja literału o wartości null

Console.ReadLine może w rzadkich sytuacjach zwrócić brak wartości zamiast tekstu — na przykład gdy dane wejściowe pochodzą z pliku, który właśnie się skończył. Kompilator o tym wie i uprzedza, że zmienna string mogłaby zostać pusta w sensie „nie ma nic”.

RozwiązanieKiedy
string imie = Console.ReadLine() ?? ""; Zalecane. Operator ?? mówi: „jeśli po lewej nic nie ma, użyj tego z prawej”. Zmienna zawsze dostanie jakiś napis, choćby pusty.
string? imie = Console.ReadLine(); Znak zapytania po typie oznacza „ta zmienna może nie mieć wartości”. Poprawne, ale wtedy trzeba to sprawdzać przy każdym użyciu.
zignorować ostrzeżenie W programie uruchamianym z klawiatury ReadLine praktycznie nigdy nie zwróci braku wartości. Na potrzeby nauki to dopuszczalne — ale lepiej wyrobić sobie nawyk pisania ?? "".

W tym kursie będziemy pisać ?? "". To jeden znak więcej, a kod jest wtedy bez zarzutu.

TEORIA

Zachęta, czyli jak zapytać użytkownika

Program, który po prostu zatrzymuje się i miga kursorem, jest bezużyteczny. Użytkownik musi wiedzieć, czego od niego chcesz.

Program.cs
// ✗ Kursor w nowej linii — wygląda, jakby program się zawiesił
Console.WriteLine("Podaj wiek:");
string a = Console.ReadLine() ?? "";

// ✓ Kursor tuż za pytaniem — naturalnie zaprasza do pisania
Console.Write("Podaj wiek: ");
string b = Console.ReadLine() ?? "";

Trzy zasady dobrej zachęty

1. Używaj Console.Write, nie WriteLine — kursor zostaje w tej samej linii.

2. Zostaw spację na końcu"Podaj wiek: ", żeby wpisany tekst nie kleił się do dwukropka.

3. Podaj oczekiwany format"Podaj wiek (0-120): " albo "Podaj cene (np. 19,99): ". Użytkownik nie zgadnie, czy ma wpisać kropkę czy przecinek.

TEORIA

Wczytywanie liczb

Skoro ReadLine zwraca tekst, potrzebny jest drugi krok — konwersja. Z poprzedniej lekcji wiesz już, że dla danych od użytkownika używamy TryParse.

Program.cs
Console.Write("Podaj wiek: ");
string tekst = Console.ReadLine() ?? "";

bool sieUdalo = int.TryParse(tekst, out int wiek);

Console.WriteLine($"Wczytano poprawnie: {sieUdalo}");
Console.WriteLine($"Wartosc: {wiek}");

Można to skrócić i wczytać od razu w jednej linii:

Program.cs
Console.Write("Podaj wiek: ");
bool sieUdalo = int.TryParse(Console.ReadLine(), out int wiek);

Tutaj ?? "" nie jest potrzebne

TryParse przyjmuje brak wartości bez protestu — po prostu zwraca wtedy false. Ostrzeżenie CS8600 pojawia się tylko wtedy, gdy przypisujesz wynik ReadLine do zmiennej typu string.

Liczby z częścią ułamkową

Program.cs
Console.Write("Podaj wzrost w metrach (np. 1,75): ");
bool ok = double.TryParse(Console.ReadLine(), out double wzrost);

Console.WriteLine($"Wczytano: {wzrost}");

Przecinek, nie kropka

Na polskim systemie double.TryParse oczekuje przecinka. Użytkownik, który wpisze 1.75, dostanie false — i pomyśli, że program jest zepsuty.

Dlatego zawsze podawaj przykład w zachęcie: "(np. 1,75)". W następnej lekcji pokażemy, jak przyjmować oba zapisy naraz.

TEORIA

Sprawdzanie, czy dane mają sens

Udana konwersja to dopiero połowa sukcesu. Liczba -500 jest poprawną liczbą całkowitą, ale nie jest poprawnym wiekiem.

Przedsmak instrukcji warunkowej

Do sprawdzania danych potrzebujemy instrukcji if. Poznasz ją dokładnie w lekcji 12, ale zasada jest tak prosta, że możemy z niej skorzystać już teraz:

if (warunek) { ... } else { ... } — jeśli warunek jest prawdziwy, wykona się pierwszy blok; w przeciwnym razie drugi. Warunki budujesz z operatorów poznanych w lekcji 08.

Program.cs
Console.Write("Podaj wiek (0-120): ");
bool sieUdalo = int.TryParse(Console.ReadLine(), out int wiek);

if (!sieUdalo)
{
    Console.WriteLine("To nie jest liczba calkowita.");
}
else if (wiek < 0 || wiek > 120)
{
    Console.WriteLine("Wiek musi miescic sie w zakresie 0-120.");
}
else
{
    Console.WriteLine($"Dziekuje. Za 10 lat bedziesz mial {wiek + 10} lat.");
}
WpisaneKtóry blok się wykonaDlaczego
abcpierwszykonwersja się nie udała
-5drugikonwersja OK, ale wartość poza zakresem
200drugijak wyżej
17trzeciwszystko w porządku

Dwa różne pytania, dwa różne komunikaty

Rozdzielenie „to nie jest liczba” od „liczba jest poza zakresem” to nie pedanteria. Użytkownik, który dostanie właściwy komunikat, poprawi się za pierwszym razem. Komunikat „błędne dane” nie mówi mu nic.

Czego tu jeszcze brakuje

Ten program mówi, co jest nie tak — ale kończy się i nie daje drugiej szansy. Żeby prosić o dane do skutku, potrzebna jest pętla. Wrócimy do tego w lekcji 14 i wtedy dopiszemy brakujący fragment.

METODY WBUDOWANE

Pozostałe metody klasy Console

MetodaZwracaDziałanie
Console.ReadLine()string? Czeka na wpisanie tekstu i klawisz Enter. Zwraca wpisany tekst bez znaku końca linii.
Console.Write(x)nic Wypisuje tekst, kursor zostaje w tej samej linii. Do zachęt.
Console.WriteLine(x)nic Wypisuje tekst i przechodzi do nowej linii. Bez argumentu robi pustą linię.
Console.ReadKey()ConsoleKeyInfo Czeka na jeden klawisz, bez Entera. Przydatne do „naciśnij dowolny klawisz”. Wywołanie Console.ReadKey(true) nie wyświetla naciśniętego znaku.
Console.Clear()nic Czyści okno konsoli. Uwaga: w niektórych terminalach nie działa albo tylko przewija ekran.
Console.Titlestring Właściwość — ustawia napis na pasku tytułu okna: Console.Title = "Kalkulator";.
Console.ForegroundColorkolor Zmienia kolor tekstu: Console.ForegroundColor = ConsoleColor.Red;. Przywrócenie: Console.ResetColor().
Program.cs
Console.Title = "Moj program";

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Operacja zakonczona powodzeniem.");
Console.ResetColor();

Console.WriteLine();
Console.Write("Nacisnij dowolny klawisz...");
Console.ReadKey(true);

Kolorów używaj oszczędnie

Zielony przy powodzeniu i czerwony przy błędzie — tak. Tęcza w każdej linii sprawia, że program wygląda amatorsko i gorzej się czyta. Pamiętaj też o ResetColor(), inaczej kolor zostanie ustawiony aż do zamknięcia okna.

PRZYKŁAD Z OMÓWIENIEM

Kalkulator BMI

Program pyta o imię, wzrost i wagę, sprawdza poprawność danych i liczy wskaźnik masy ciała. Wszystko, czego nauczyłeś się w tym bloku, w jednym miejscu.

Program.cs
Console.Title = "Kalkulator BMI";

Console.WriteLine("=== KALKULATOR BMI ===");
Console.WriteLine();

// Imię — dowolny tekst, wystarczy sprawdzić, czy coś wpisano
Console.Write("Imie: ");
string imie = Console.ReadLine() ?? "";

if (imie.Length == 0)
{
    imie = "Nieznajomy";
}

// Wzrost — liczba z ułamkiem, w sensownym zakresie
Console.Write("Wzrost w metrach (np. 1,75): ");
bool okWzrost = double.TryParse(Console.ReadLine(), out double wzrost);

// Waga — j.w.
Console.Write("Waga w kilogramach (np. 68,5): ");
bool okWaga = double.TryParse(Console.ReadLine(), out double waga);

Console.WriteLine();

// Sprawdzamy wszystko naraz
if (!okWzrost || !okWaga)
{
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("Blad: wzrost i waga musza byc liczbami (z przecinkiem).");
    Console.ResetColor();
}
else if (wzrost <= 0.5 || wzrost >= 2.5 || waga <= 20 || waga >= 400)
{
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("Blad: podane wartosci sa poza realnym zakresem.");
    Console.ResetColor();
}
else
{
    double bmi = waga / (wzrost * wzrost);

    Console.WriteLine($"{imie}, twoje BMI wynosi {bmi:F1}");
    Console.WriteLine();
    Console.WriteLine("Zakresy orientacyjne:");
    Console.WriteLine("  ponizej 18,5  - niedowaga");
    Console.WriteLine("  18,5 - 24,9   - waga prawidlowa");
    Console.WriteLine("  25,0 - 29,9   - nadwaga");
    Console.WriteLine("  30,0 i wiecej - otylosc");
}

Console.WriteLine();
Console.Write("Nacisnij dowolny klawisz...");
Console.ReadKey(true);
wynik w konsoli
=== KALKULATOR BMI ===

Imie: Anna
Wzrost w metrach (np. 1,75): 1,68
Waga w kilogramach (np. 68,5): 59

Anna, twoje BMI wynosi 20,9

Zakresy orientacyjne:
  ponizej 18,5  - niedowaga
  18,5 - 24,9   - waga prawidlowa
  25,0 - 29,9   - nadwaga
  30,0 i wiecej - otylosc

Nacisnij dowolny klawisz...

Omówienie

FragmentDlaczego tak
Console.ReadLine() ?? "" Usuwa ostrzeżenie CS8600. Gdyby ReadLine nie zwróciło nic, zmienna dostanie pusty napis zamiast braku wartości.
if (imie.Length == 0) Puste imię nie jest błędem, więc nie przerywamy programu — po prostu podstawiamy wartość zastępczą. Nie każdy problem z danymi musi kończyć się komunikatem o błędzie.
Zachęty z przykładem "(np. 1,75)" od razu pokazuje, że separatorem jest przecinek. Bez tego połowa użytkowników wpisze kropkę i zobaczy komunikat o błędzie.
if (!okWzrost || !okWaga) Najpierw sprawdzamy, czy w ogóle udało się wczytać liczby. Dopiero potem ma sens pytanie o ich zakres.
Zakres 0.5 – 2.5 metra Walidacja nie musi być idealna — ma odsiać wartości oczywiście bezsensowne, jak wzrost 175 (metrów, bo ktoś podał centymetry).
waga / (wzrost * wzrost) Nawias jest konieczny. Bez niego dzielenie i mnożenie wykonałyby się od lewej: (waga / wzrost) * wzrost, co dałoby po prostu wagę.
{bmi:F1} Jedno miejsce po przecinku wystarczy — BMI podaje się z taką dokładnością, a bez formatu zobaczylibyśmy 20,904761904761905.
Console.ReadKey(true) Argument true sprawia, że naciśnięty klawisz nie pojawia się na ekranie. Bez niego w oknie zostałaby przypadkowa litera.
CZĘSTE BŁĘDY

Na co uważać

KodProblem
int wiek = Console.ReadLine(); CS0029ReadLine zwraca tekst, nie liczbę. Potrzebna konwersja.
int wiek = int.Parse(Console.ReadLine()); Kompiluje się, ale przerwie program, gdy użytkownik wpisze cokolwiek innego niż liczbę. Użyj TryParse.
Console.WriteLine("Podaj wiek: "); Nie błąd, ale kursor przeskoczy do nowej linii i program będzie wyglądał na zawieszony. Do zachęt używaj Write.
Console.Write("Podaj wiek:"); Brak spacji na końcu — wpisany tekst sklei się z dwukropkiem.
zachęta bez formatu "Podaj wzrost: " nie mówi, czy chodzi o metry czy centymetry ani czy pisać kropkę czy przecinek. Dodaj przykład.
sprawdzenie zakresu bez sprawdzenia konwersji Gdy TryParse zawiedzie, zmienna dostaje zero — a zero często mieści się w dozwolonym zakresie. Najpierw pytaj, czy konwersja się udała.
Console.ReadKey() bez argumentu Naciśnięty klawisz wypisze się na ekranie. Użyj ReadKey(true).
brak ResetColor() Ustawiony kolor zostaje do końca działania programu — także dla komunikatów systemowych.
ZADANIA

Zadania

ZAD 1Powitanie★☆☆

Program pyta o imię i rok urodzenia, po czym wypisuje powitanie z obliczonym wiekiem. Zadbaj o poprawne zachęty.

pokaż rozwiązanie
const int BiezacyRok = 2026;

Console.Write("Imie: ");
string imie = Console.ReadLine() ?? "";

Console.Write("Rok urodzenia (np. 2008): ");
bool ok = int.TryParse(Console.ReadLine(), out int rok);

if (!ok)
{
    Console.WriteLine("Rok musi byc liczba.");
}
else
{
    int wiek = BiezacyRok - rok;
    Console.WriteLine($"Czesc {imie}! W tym roku konczysz {wiek} lat.");
}

Rok bieżący trzymamy w stałej, a nie wpisujemy wprost w obliczenie — dzięki temu w przyszłym roku poprawiasz jedną linię. Program mógłby pobrać go z systemu przez DateTime.Now.Year, ale do tego wrócimy przy datach.

ZAD 2Zamiana jednostek★☆☆

Wczytaj długość w centymetrach i wypisz ją w metrach, calach i stopach. Przelicznik: 1 cal = 2,54 cm, 1 stopa = 30,48 cm.

pokaż rozwiązanie
const double CmNaCal = 2.54;
const double CmNaStope = 30.48;

Console.Write("Podaj dlugosc w centymetrach: ");
bool ok = double.TryParse(Console.ReadLine(), out double cm);

if (!ok || cm < 0)
{
    Console.WriteLine("Podaj poprawna, nieujemna liczbe.");
}
else
{
    Console.WriteLine($"{cm} cm to:");
    Console.WriteLine($"  {cm / 100,8:F2} m");
    Console.WriteLine($"  {cm / CmNaCal,8:F2} cala");
    Console.WriteLine($"  {cm / CmNaStope,8:F2} stopy");
}
Podaj dlugosc w centymetrach: 175
175 cm to:
      1,75 m
     68,90 cala
      5,74 stopy

Warunek !ok || cm < 0 łączy dwa sprawdzenia: konwersję i sens wartości. Dzięki ocenie skróconej, gdy konwersja zawiedzie, drugi warunek nie jest nawet sprawdzany.

Liczba 8 w klamrach wyrównuje wyniki do prawej, żeby przecinki ustawiły się jeden pod drugim.

ZAD 3Kalkulator dwóch liczb★★☆

Wczytaj dwie liczby całkowite i wypisz ich sumę, różnicę, iloczyn oraz iloraz z dokładnością do dwóch miejsc. Zabezpiecz się przed dzieleniem przez zero.

pokaż rozwiązanie
Console.Write("Pierwsza liczba: ");
bool ok1 = int.TryParse(Console.ReadLine(), out int a);

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

Console.WriteLine();

if (!ok1 || !ok2)
{
    Console.WriteLine("Obie wartosci musza byc liczbami calkowitymi.");
}
else
{
    Console.WriteLine($"{a} + {b} = {a + b}");
    Console.WriteLine($"{a} - {b} = {a - b}");
    Console.WriteLine($"{a} * {b} = {a * b}");

    if (b == 0)
    {
        Console.WriteLine($"{a} / {b} = nie mozna dzielic przez zero");
    }
    else
    {
        Console.WriteLine($"{a} / {b} = {(double)a / b:F2}");
    }
}

Rzutowanie (double)a jest konieczne — bez niego dostalibyśmy dzielenie całkowite i format F2 pokazałby na przykład 3,00 zamiast 3,50.

Sprawdzenie b == 0 musi być przed dzieleniem. Nie da się tego zrobić po fakcie — program przerwałby działanie wcześniej.

ZAD 4Rachunek za prąd★★☆

Wczytaj stan licznika z poprzedniego i bieżącego miesiąca oraz cenę za kilowatogodzinę. Oblicz zużycie i kwotę do zapłaty. Sprawdź, czy nowy stan nie jest mniejszy od poprzedniego.

pokaż rozwiązanie
Console.Write("Stan poprzedni (kWh): ");
bool ok1 = int.TryParse(Console.ReadLine(), out int poprzedni);

Console.Write("Stan biezacy (kWh): ");
bool ok2 = int.TryParse(Console.ReadLine(), out int biezacy);

Console.Write("Cena za kWh (np. 0,89): ");
bool ok3 = decimal.TryParse(Console.ReadLine(), out decimal cena);

Console.WriteLine();

if (!ok1 || !ok2 || !ok3)
{
    Console.WriteLine("Wszystkie wartosci musza byc liczbami.");
}
else if (biezacy < poprzedni)
{
    Console.WriteLine("Stan biezacy nie moze byc mniejszy od poprzedniego.");
}
else if (cena <= 0)
{
    Console.WriteLine("Cena musi byc dodatnia.");
}
else
{
    int zuzycie = biezacy - poprzedni;
    decimal doZaplaty = zuzycie * cena;

    Console.WriteLine($"Zuzycie:    {zuzycie} kWh");
    Console.WriteLine($"Cena:       {cena:F2} zl/kWh");
    Console.WriteLine($"Do zaplaty: {doZaplaty:F2} zl");
}
Stan poprzedni (kWh): 14520
Stan biezacy (kWh): 14783
Cena za kWh (np. 0,89): 0,89

Zuzycie:    263 kWh
Cena:       0,89 zl/kWh
Do zaplaty: 234,07 zl

Trzy osobne komunikaty dla trzech różnych problemów — użytkownik od razu wie, co poprawić.

Cenę trzymamy w decimal, bo to pieniądze. Zużycie w int, bo licznik pokazuje pełne kilowatogodziny. Mnożenie int * decimal działa bez rzutowania.

ZAD 5Ankieta★★★

Napisz krótką ankietę: imię, wiek, ulubiony przedmiot oraz odpowiedź tak/nie na pytanie, czy uczeń lubi programować. Na końcu wypisz podsumowanie w ramce z gwiazdek. Odpowiedź „tak” ma być rozpoznawana niezależnie od wielkości liter.

pokaż rozwiązanie
Console.WriteLine("=== ANKIETA ===");
Console.WriteLine();

Console.Write("Imie: ");
string imie = Console.ReadLine() ?? "";
if (imie.Length == 0) imie = "Anonim";

Console.Write("Wiek: ");
int.TryParse(Console.ReadLine(), out int wiek);

Console.Write("Ulubiony przedmiot: ");
string przedmiot = Console.ReadLine() ?? "brak";

Console.Write("Czy lubisz programowac? (tak/nie): ");
string odpowiedz = Console.ReadLine() ?? "";
bool lubi = string.Equals(odpowiedz, "tak", StringComparison.OrdinalIgnoreCase);

Console.WriteLine();
Console.WriteLine(new string('*', 40));
Console.WriteLine($"* {"PODSUMOWANIE",-36} *");
Console.WriteLine(new string('*', 40));
Console.WriteLine($"* Imie:      {imie,-25} *");
Console.WriteLine($"* Wiek:      {wiek,-25} *");
Console.WriteLine($"* Przedmiot: {przedmiot,-25} *");
Console.WriteLine($"* Programuje z checia: {(lubi ? "TAK" : "NIE"),-15} *");
Console.WriteLine(new string('*', 40));
=== ANKIETA ===

Imie: Anna
Wiek: 17
Ulubiony przedmiot: informatyka
Czy lubisz programowac? (tak/nie): TAK

****************************************
* PODSUMOWANIE                         *
****************************************
* Imie:      Anna                      *
* Wiek:      17                        *
* Przedmiot: informatyka               *
* Programuje z checia: TAK             *
****************************************

string.Equals(..., OrdinalIgnoreCase) sprawia, że TAK, Tak i tak są traktowane tak samo.

new string('*', 40) tworzy linię z czterdziestu gwiazdek — krócej niż przepisywanie ich ręcznie.

Zapis {lubi ? "TAK" : "NIE"} to operator warunkowy: jeśli warunek przed znakiem zapytania jest prawdziwy, bierze pierwszą wartość, w przeciwnym razie drugą. To skrócona forma instrukcji if — poznasz ją dokładnie w lekcji 12. Zwróć uwagę na nawias wokół całości: bez niego dwukropek zostałby uznany za początek formatu.

Liczby ujemne po przecinku (-25, -14) wyrównują tekst do lewej, dzięki czemu prawa krawędź ramki układa się równo.

PODSUMOWANIE

Co trzeba zapamiętać

  • Console.ReadLine() czeka na Enter i zwraca tekst — zawsze, nawet gdy użytkownik wpisze liczbę.
  • Do zachęt używaj Console.Write, ze spacją na końcu i z przykładem oczekiwanego formatu.
  • Ostrzeżenie CS8600 usuwa zapis Console.ReadLine() ?? "".
  • Liczby wczytujemy przez TryParse — nigdy przez Parse przy danych od użytkownika.
  • Najpierw sprawdź, czy konwersja się udała, dopiero potem zakres — po nieudanej konwersji zmienna zawiera zero.
  • Na polskim systemie liczby ułamkowe wczytuje się z przecinkiem.
  • Każdy rodzaj błędu zasługuje na osobny, konkretny komunikat.
  • Console.ReadKey(true) czeka na klawisz, nie wyświetlając go.
  • Po zmianie koloru tekstu pamiętaj o Console.ResetColor().