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

Konwersje typów

Użytkownik wpisuje z klawiatury tekst, a ty potrzebujesz liczby. Obliczenie daje ułamek, a ty chcesz liczbę całkowitą. Ta lekcja pokazuje, jak zamieniać jeden typ na drugi — i jak nie stracić przy tym danych ani nie wywrócić programu.

C# rzutowanie Parse TryParse 50 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wyjaśnisz, kiedy konwersja dzieje się sama, a kiedy trzeba o nią poprosić
  • Zapiszesz rzutowanie i wskażesz, jakie dane przy nim tracisz
  • Zamienisz tekst na liczbę metodą Parse i TryParse
  • Wyjaśnisz, dlaczego TryParse jest bezpieczniejsze
  • Zamienisz liczbę na tekst i nadasz jej format
  • Wskażesz różnicę między Parse, TryParse a klasą Convert
TEORIA

Po co w ogóle konwersje

C# pilnuje typów bardzo rygorystycznie. Nie pozwoli włożyć tekstu do zmiennej liczbowej ani wykonać działania na niepasujących typach.

Program.cs
string wiekTekst = "17";

// int rok = 2026 - wiekTekst;
//                  └── CS0019: nie można odjąć tekstu od liczby

Program musi więc najpierw przekształcić napis "17" w liczbę 17. Taka zamiana jednego typu w drugi to konwersja.

To nie jest to samo, co „zmiana typu zmiennej”

Typu istniejącej zmiennej nie da się zmienić — on jest ustalony raz na zawsze. Konwersja tworzy nową wartość innego typu na podstawie starej. Oryginał zostaje nietknięty.

W C# konwersje dzielą się na dwie grupy: te, które kompilator wykona sam, i te, o które musisz poprosić.

TEORIA

Konwersja niejawna — dzieje się sama

Konwersja niejawna jest dozwolona przez reguły języka bez zapisywania rzutowania. Na przykład int → long zachowuje wartość dokładnie, ale int → float oraz long → double mogą utracić precyzję. Większy zakres nie oznacza większej dokładności.

Program.cs
int liczba = 42;

long dluga = liczba;        // int  → long    zawsze bezpieczne
double ulamek = liczba;     // int  → double  zawsze bezpieczne
decimal kwota = liczba;     // int  → decimal zawsze bezpieczne

byte maly = 200;
int wiekszy = maly;         // byte → int     zawsze bezpieczne

Console.WriteLine($"{dluga} {ulamek} {kwota} {wiekszy}");   // 42 42 42 200

Kierunki, w których konwersja jest automatyczna

kierunek poszerzania
byte  →  short  →  int  →  long  →  float  →  double
                    ↓        ↓
                    └────────┴──→  decimal

char  →  int  (i dalej jak wyżej)
ZasadaWyjaśnienie
Węższy w szerszy — takKażda liczba typu int zmieści się w long. Nie ma ryzyka.
Całkowity w zmiennoprzecinkowy — takint wchodzi do double i decimal automatycznie.
Zmiennoprzecinkowy w decimal — niedouble ma szerszy zakres niż decimal, więc automatu nie ma. Trzeba rzutować.
decimal w double — nieStraciłbyś dokładność, na której właśnie zależało. Trzeba rzutować świadomie.
Cokolwiek na string — nieTekst to zupełnie inna kategoria. Służą do tego metody, nie rzutowanie.

Bezpieczne nie znaczy dokładne

int → float jest automatyczne, bo każda liczba int mieści się w zakresie float. Ale float ma tylko około 7 cyfr znaczących, więc bardzo duże liczby całkowite tracą precyzję:

int duza = 16777217; po zamianie na float i z powrotem daje 16777216.

Dlatego do liczb całkowitych, które mają zostać dokładne, nie używa się float.

TEORIA

Rzutowanie — gdy coś może się zgubić

Gdy nowy typ jest węższy, kompilator odmawia współpracy. Musisz sam potwierdzić, że wiesz, co robisz — służy do tego rzutowanie: nazwa typu w nawiasie przed wartością.

Program.cs
double cena = 123.87;

// int calkowita = cena;              // CS0266 — kompilator odmawia
int calkowita = (int)cena;            // ✓ prosimy wprost

Console.WriteLine(calkowita);         // 123 — część ułamkowa UCIĘTA, nie zaokrąglona

Rzutowanie ucina, nie zaokrągla

(int)123.87 daje 123, a nie 124. Rzutowanie po prostu odrzuca wszystko po przecinku, niezależnie od tego, jak blisko było do następnej liczby. (int)0.99 daje zero.

Jeśli chcesz zaokrąglić, użyj Math.Round — a dopiero potem rzutuj: (int)Math.Round(123.87) daje 124.

Zawężanie liczb całkowitych

Program.cs
int mala = 200;
byte b1 = (byte)mala;         // 200 — mieści się, wszystko dobrze
Console.WriteLine(b1);        // 200

int duza = 300;
byte b2 = (byte)duza;         // 300 NIE mieści się w bajcie!
Console.WriteLine(b2);        // 44  — i nikt nie ostrzega

Skąd 44? Bajt mieści liczby 0–255, więc 300 „przekręca się” o pełny zakres: 300 − 256 = 44. To ten sam mechanizm, który poznałeś w lekcji 06.

Rzutowanie to obietnica, którą składasz kompilatorowi

Pisząc (byte), mówisz: „sprawdziłem, ta wartość się zmieści”. Kompilator ci wierzy i przestaje pilnować. Jeśli obietnica była fałszywa, dostaniesz cichy, błędny wynik — bez żadnego komunikatu.

Dlatego przed rzutowaniem warto sprawdzić zakres albo użyć słowa checked, które zamienia ciche przekręcenie w wyjątek.

TEORIA

Liczba na tekst — zawsze się udaje

To najprostszy kierunek. Każda wartość w C# potrafi opisać się tekstem metodą ToString().

Program.cs
int liczba = 42;
double ulamek = 3.14159;
bool flaga = true;

string t1 = liczba.ToString();       // "42"
string t2 = ulamek.ToString();       // "3,14159"
string t3 = flaga.ToString();        // "True"

// Z formatem
string t4 = ulamek.ToString("F2");   // "3,14"

// Najczęściej i tak używamy interpolacji — ona wywołuje ToString za nas
Console.WriteLine($"{liczba} {ulamek:F2} {flaga}");   // 42 3,14 True

Wartość logiczna zamienia się na "True" lub "False"

Z wielkiej litery i po angielsku — tak zdefiniowano to w bibliotece .NET. Jeśli chcesz pokazać użytkownikowi „tak” i „nie”, musisz to zrobić sam, instrukcją warunkową. Poznasz ją w następnym bloku.

TEORIA

Tekst na liczbę — tu bywa trudno

Ten kierunek jest ryzykowny, bo nie każdy tekst da się zamienić na liczbę. Co ma zrobić program, gdy dostanie "abc" albo pusty napis? C# daje dwie odpowiedzi na to pytanie.

Sposób 1: Parse — przerywa program przy błędzie

Program.cs
string tekst = "123";
int liczba = int.Parse(tekst);
Console.WriteLine(liczba + 1);        // 124

// A gdy tekst nie jest liczbą:
string zly = "sto dwadziescia trzy";
// int x = int.Parse(zly);            // FormatException — program się wywraca

Sposób 2: TryParse — mówi, czy się udało

Program.cs
string tekst = "123";

bool sukces = int.TryParse(tekst, out int wynik);

Console.WriteLine(sukces);      // True
Console.WriteLine(wynik);       // 123

// Przy błędnym tekście program działa dalej
bool sukces2 = int.TryParse("abc", out int wynik2);

Console.WriteLine(sukces2);     // False
Console.WriteLine(wynik2);      // 0 — wartość domyślna, bo konwersja się nie udała

Co robi słowo out

TryParse musi zwrócić dwie informacje naraz: czy się udało oraz jaki jest wynik. Metoda potrafi zwrócić tylko jedną wartość, więc druga jest przekazywana przez parametr oznaczony słowem out.

Zapis out int wynik jednocześnie deklaruje zmienną i przekazuje ją do metody, żeby ta mogła ją wypełnić. Po wywołaniu zmienna wynik jest dostępna w twoim kodzie. Do parametrów out wrócimy dokładniej w bloku o metodach.

Które wybrać

MetodaGdy tekst jest błędnyKiedy używać
int.Parse(t) przerywa program wyjątkiem Gdy masz pewność, że tekst jest poprawny — np. pochodzi z twojego własnego kodu, nie od użytkownika.
int.TryParse(t, out x) zwraca false, program działa dalej Zawsze przy danych od użytkownika, z pliku albo z sieci — czyli wszędzie tam, gdzie błąd jest normalną możliwością.

Zasada na cały kurs

Cokolwiek wpisuje użytkownik — TryParse. Nie dlatego, że Parse jest złe, tylko dlatego, że użytkownik prędzej czy później wpisze coś nieoczekiwanego, a program nie powinien się od tego wywracać.

Pozostałe typy działają tak samo

Program.cs
double.TryParse("3,14", out double d);     // przecinek — polskie ustawienia
decimal.TryParse("19,99", out decimal m);
bool.TryParse("true", out bool b);         // działa też "True", "TRUE"
long.TryParse("9000000000", out long l);

Przecinek czy kropka?

double.TryParse patrzy na ustawienia regionalne systemu. Na polskim Windowsie separatorem części ułamkowej jest przecinek, więc "3,14" zadziała, a "3.14" zostanie odrzucone albo — co gorsza — zrozumiane jako 314.

To bywa źródłem trudnych do znalezienia błędów, zwłaszcza przy czytaniu plików utworzonych na innym komputerze. Jak to opanować, pokazujemy w następnej lekcji, przy okazji formatowania.

METODY WBUDOWANE

Klasa Convert

Trzecia droga: klasa Convert z zestawem metod zamieniających dowolny typ na dowolny inny.

MetodaZwracaUwagi
Convert.ToInt32(x)intPrzyjmuje tekst, liczbę, wartość logiczną. Dla null zwraca 0 zamiast błędu.
Convert.ToDouble(x)double—
Convert.ToDecimal(x)decimal—
Convert.ToBoolean(x)boolDla liczb: 0 to false, każda inna to true.
Convert.ToString(x)stringZ drugim argumentem zamienia liczbę na inny system: Convert.ToString(20, 2) daje "10100".
Program.cs
// Convert zaokrągla, a rzutowanie ucina — to ważna różnica!
double x = 4.7;

Console.WriteLine((int)x);                 // 4  — ucięcie
Console.WriteLine(Convert.ToInt32(x));     // 5  — zaokrąglenie

// Convert radzi sobie z brakiem wartości
string brak = null;
Console.WriteLine(Convert.ToInt32(brak));  // 0  — zamiast błędu
SposóbPrzy błędnym tekściePrzy liczbie z ułamkiem
(int) — rzutowanienie dotyczyucina część ułamkową
int.Parsewyjąteknie dotyczy
int.TryParsezwraca falsenie dotyczy
Convert.ToInt32wyjątekzaokrągla

Czego używać w praktyce

Do danych od użytkownika — zawsze TryParse.
Do zamiany liczby na liczbę — rzutowanie, ewentualnie Math.Round plus rzutowanie, gdy chcesz zaokrąglić.
Convert przydaje się głównie tam, gdzie wartość może być pusta, oraz do zamiany systemów liczbowych.

TEORIA

Znak a liczba

Typ char to w gruncie rzeczy liczba — numer znaku w tablicy Unicode. Dlatego konwersje między char a int są bardzo proste.

Program.cs
char litera = 'A';

int kod = litera;                 // char → int, automatycznie: 65
char zKodu = (char)66;            // int → char, rzutowanie: 'B'

Console.WriteLine($"{kod} {zKodu}");     // 65 B

// Następna litera alfabetu
char nastepna = (char)(litera + 1);      // 'B'
Console.WriteLine(nastepna);

Znak cyfry to nie jest ta cyfra

Znak '7' ma w tablicy Unicode numer 55, a nie 7. To bardzo częste źródło pomyłek.

Program.cs
char cyfra = '7';

Console.WriteLine((int)cyfra);          // 55  ✗ numer znaku, nie wartość

// Sposób 1: odejmujemy numer znaku zera
int wartosc1 = cyfra - '0';             // 55 - 48 = 7   ✓

// Sposób 2: gotowa metoda
int wartosc2 = (int)char.GetNumericValue(cyfra);   // 7  ✓

Console.WriteLine($"{wartosc1} {wartosc2}");       // 7 7

Sztuczka z odejmowaniem '0' działa, bo cyfry mają w tablicy Unicode kolejne numery: '0' to 48, '1' to 49, i tak dalej aż do '9' = 57. Odjęcie numeru zera daje więc wartość cyfry.

PRZYKŁAD Z OMÓWIENIEM

Paragon — wszystkie konwersje naraz

Program dostaje dane w postaci tekstu (tak, jakby przyszły z formularza), zamienia je na liczby, liczy i wypisuje wynik z powrotem jako tekst.

Program.cs
// Dane „z formularza" — zawsze przychodzą jako tekst
string sztukTekst = "3";
string cenaTekst = "24,99";
string rabatTekst = "abc";        // celowo błędny

const decimal StawkaVat = 0.23m;

// Konwersje z zabezpieczeniem
bool okSztuk = int.TryParse(sztukTekst, out int sztuk);
bool okCena = decimal.TryParse(cenaTekst, out decimal cenaJednostkowa);
bool okRabat = decimal.TryParse(rabatTekst, out decimal rabatProcent);

Console.WriteLine($"Sztuki wczytane: {okSztuk}  -> {sztuk}");
Console.WriteLine($"Cena wczytana:   {okCena}  -> {cenaJednostkowa}");
Console.WriteLine($"Rabat wczytany:  {okRabat} -> {rabatProcent}");
Console.WriteLine();

// Obliczenia — sztuk to int, cena to decimal; int wchodzi w decimal automatycznie
decimal netto = sztuk * cenaJednostkowa;
decimal vat = netto * StawkaVat;
decimal brutto = netto + vat;

// Ile pełnych sztuk zmieści się w budżecie 50 zl
decimal budzet = 50m;
int zaBudzet = (int)(budzet / cenaJednostkowa);      // rzutowanie UCINA — i o to chodzi

Console.WriteLine($"Netto:  {netto,8:F2} zl");
Console.WriteLine($"VAT:    {vat,8:F2} zl");
Console.WriteLine($"Brutto: {brutto,8:F2} zl");
Console.WriteLine();
Console.WriteLine($"Za {budzet:F2} zl kupisz {zaBudzet} szt.");
wynik w konsoli
Sztuki wczytane: True  -> 3
Cena wczytana:   True  -> 24,99
Rabat wczytany:  False -> 0

Netto:     74,97 zl
VAT:       17,24 zl
Brutto:    92,21 zl

Za 50,00 zl kupisz 2 szt.

Omówienie

FragmentDlaczego tak
Wszystkie dane jako string Tak wygląda rzeczywistość: z klawiatury, z pliku i z formularza dane przychodzą jako tekst. Konwersja jest zawsze pierwszym krokiem.
TryParse zamiast Parse Celowo podaliśmy błędny rabat. Program nie wywrócił się — po prostu zapisał, że konwersja się nie udała, a zmienna dostała zero.
sztuk * cenaJednostkowa int pomnożony przez decimal daje decimal — kompilator sam poszerza węższy typ. Rzutowanie nie jest potrzebne.
(int)(budzet / cenaJednostkowa) 50 / 24,99 to około 2,0008. Rzutowanie ucina do 2 — i tego właśnie chcemy, bo nie kupimy dwóch sztuk i ułamkowej części kolejnej. To przykład, w którym ucinanie jest zaletą, a nie pułapką.
50m 50m jest literałem decimal. Samo 50 jest literałem int i może być niejawnie przekonwertowane do decimal; przyrostek nie jest tu konieczny. Natomiast 50.0 bez m byłoby double.
{netto,8:F2} Dwa formaty naraz: 8 to szerokość kolumny (wyrównanie do prawej), F2 to dwa miejsca po przecinku. Dzięki temu kwoty ładnie się układają jedna pod drugą.
CZĘSTE BŁĘDY

Na co uważać

KodProblem
int x = 3.99; CS0266 — konwersja zawężająca wymaga rzutowania: (int)3.99.
int x = (int)3.99; licząc na 4 Da 3. Rzutowanie ucina. Do zaokrąglenia użyj (int)Math.Round(3.99).
int.Parse(Console.ReadLine()) Kompiluje się, ale przerwie program, gdy użytkownik wpisze cokolwiek innego niż liczbę. Użyj TryParse.
double.Parse("3.14") na polskim systemie Kropka nie jest tu separatorem ułamka. Albo dane muszą używać przecinka, albo trzeba podać kulturę — o tym w następnej lekcji.
int w = cyfra; gdy cyfra = '7' Da 55, nie 7. Trzeba cyfra - '0'.
string s = (string)liczba; CS0030 — na tekst się nie rzutuje. Użyj liczba.ToString() albo interpolacji.
byte b = (byte)300; Kompiluje się, ale da 44. Rzutowanie nie ostrzega przed przekroczeniem zakresu.
decimal d = 19.99; CS0664 — literał bez przyrostka jest typu double, a double → decimal nie jest automatyczne. Potrzebne 19.99m.
if (int.TryParse(t, out int x)) { }
a potem użycie x na zewnątrz
Zmienna zadeklarowana w wywołaniu TryParse jest widoczna od tego miejsca do końca bloku — poza blokiem już nie. To ten sam zasięg, który poznałeś w lekcji 04.
ZADANIA

Zadania

ZAD 1Przewidz wynik★☆☆

Zapisz wynik każdej linii, zanim uruchomisz program.

co wypisze?
Console.WriteLine((int)9.99);
Console.WriteLine((int)(-9.99));
Console.WriteLine(Convert.ToInt32(9.99));
Console.WriteLine((int)Math.Round(9.5));
Console.WriteLine((char)(65 + 5));
Console.WriteLine('9' - '0');
Console.WriteLine(unchecked((byte)260)); // unchecked pozwala tu jawnie zawinac stala do zakresu byte
ZAD 2Bezpieczne wczytanie★☆☆

Masz trzy napisy: "42", "3,5" i "pies". Spróbuj zamienić każdy z nich na int metodą TryParse i wypisz, czy się udało oraz jaki wyszedł wynik.

ZAD 3Ucinanie kontra zaokrąglanie★★☆

Dla liczb 2,3, 2,5 i 2,7 wypisz obok siebie wynik rzutowania (int), Math.Round, Math.Floor i Math.Ceiling. Ułóż to w czytelną tabelkę.

ZAD 4Kalkulator kieszonkowego★★☆

Dane przychodzą jako tekst: kwota tygodniowa "37,50" i liczba tygodni "8". Oblicz łączną kwotę oraz ile pełnych gier po 149,99 zł da się za nią kupić. Użyj TryParse i odpowiednich typów.

ZAD 5Suma cyfr z tekstu★★★

Mając napis "48317", oblicz sumę jego cyfr. Nie używaj pętli — sięgaj po kolejne znaki przez indeks i zamieniaj je na wartości liczbowe.

ZAD 6Znajdź cztery błędy★★★

Program ma policzyć średnią cenę trzech produktów i pokazać ją w groszach. Nie działa poprawnie — znajdź i popraw cztery usterki.

do poprawienia
string c1 = "12,50", c2 = "8,99", c3 = "abc";

decimal cena1 = decimal.Parse(c1);
decimal cena2 = decimal.Parse(c2);
decimal cena3 = decimal.Parse(c3);

decimal srednia = cena1 + cena2 + cena3 / 3;
int grosze = srednia * 100;

Console.WriteLine("Srednia w groszach: " + grosze);
PODSUMOWANIE

Co trzeba zapamiętać

  • Konwersja niejawna nie wymaga zapisu rzutowania. Może jednak utracić precyzję, np. long → double.
  • Rzutowanie zapisujemy jako (typ)wartosc. Nie gwarantuje poprawnego zakresu.
  • Rzutowanie float/double/decimal na int obcina w stronę zera, o ile wynik mieści się w zakresie.
  • Convert.ToInt32 dla liczby ułamkowej zaokrągla, domyślnie z połówkami do parzystej.
  • unchecked umożliwia zawijanie konwersji całkowitoliczbowej; checked wykrywa przekroczenie zakresu.
  • ToString i interpolacja formatują wartości jako tekst. Rzutowanie nie jest uniwersalnym formatowaniem; np. (string)obiekt działa tylko przy odpowiednim rzeczywistym typie obiektu.
  • Parse może zgłosić wyjątek. TryParse daje bool i wynik out, dlatego jest wygodne dla danych użytkownika.
  • cyfra – '0′ daje wartość cyfry ASCII po wcześniejszym sprawdzeniu zakresu '0’–’9′.
Postęp zapisuje się w tej przeglądarce.