🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Konwersje typów w C# — casting, Parse, TryParse i Convert bez pomyłek

C# daje ci co najmniej cztery różne narzędzia do zamiany jednego typu na drugi — rzutowanie (int)int.Parse()int.TryParse() i Convert.ToInt32() — i wszystkie wyglądają, jakby robiły to samo. Nie robią. Różnice ujawniają się dopiero na granicznych przypadkach: null, liczba zmiennoprzecinkowa z ułamkiem, tekst od użytkownika z przecinkiem zamiast kropki. Ten artykuł zakłada, że znasz już podstawową konwersję z Zmiennych i typów danych — tu wchodzimy głębiej, w miejsca, gdzie realnie coś się psuje.

Niejawna (implicit) vs jawna (explicit) konwersja

int liczbaCalkowita = 150;
double liczbaZmiennoprzecinkowa = liczbaCalkowita; // ✅ niejawna — int mieści się w double bez utraty danych

double pi = 3.14;
int obcietePi = (int)pi; // trzeba jawnie — konwersja MOŻE stracić dane (część ułamkowa)

Kompilator pozwala na niejawną konwersję tylko wtedy, gdy jest bezpieczna — typ docelowy mieści cały zakres typu źródłowego (int → double zawsze się uda). Konwersja, która może stracić dane (double → intlong → int) wymaga jawnego rzutowania — to świadoma zgoda programisty na ryzyko utraty precyzji.

Pułapka: rzutowanie OBCINA, Convert ZAOKRĄGLA

double cena = 19.99;

int rzutowanie = (int)cena;         // 19 — obcina część ułamkową (w stronę zera)
int convert = Convert.ToInt32(cena); // 20 — zaokrągla do najbliższej liczby całkowitej

To jedna z najbardziej zaskakujących różnic w C# — (int) i Convert.ToInt32() robią coś innego, mimo że oba “zamieniają double na int”. Rzutowanie zawsze obcina w stronę zera (19.99 → 19, ale też -19.99 → -19, nie -20). Convert.ToInt32 zaokrągla do najbliższej liczby całkowitej, a przy dokładnie .5 używa zaokrąglenia bankierskiego (do najbliższej liczby parzystej):

Convert.ToInt32(2.5); // 2, nie 3 — zaokrąglenie do parzystej
Convert.ToInt32(3.5); // 4

Jeśli potrzebujesz konkretnego zachowania zaokrąglania (zawsze w górę, zawsze matematycznie), użyj Math.Round(cena, MidpointRounding.AwayFromZero) zamiast polegać na domyślnym zachowaniu żadnej z tych dwóch metod.

String → liczba: Parse, TryParse, Convert

string wejscie = "42";

int a = int.Parse(wejscie);              // rzuci wyjątek, jeśli string nie jest poprawną liczbą
bool ok = int.TryParse(wejscie, out int b); // bezpieczna wersja, zwraca bool zamiast wyjątku
int c = Convert.ToInt32(wejscie);         // podobne do Parse, ale...

Convert.ToInt32(null) nie rzuca wyjątku — zwraca 0int.Parse(null) rzuca ArgumentNullException. To realna, cicha różnica: kod używający Convert może “działać” z brakującymi danymi, podstawiając zero tam, gdzie Parse uczciwie by się wywalił i ujawnił problem wcześniej.

int.Parse(null);      // ❌ ArgumentNullException
Convert.ToInt32(null); // 0 — brak błędu, ale prawdopodobnie nie to, czego oczekujesz

Reguła praktyczna: dane z zewnątrz (formularz, API, plik) — zawsze TryParse. Dane, o których jesteś pewien, że są poprawne (stała w kodzie, wynik wcześniejszej walidacji) — Parse jest ok, bo wyjątek przy nieoczekiwanej wartości to sygnał realnego buga, nie coś do cichego ukrycia.

if (!int.TryParse(wejscieUzytkownika, out int wiek))
{
    Console.WriteLine("Nieprawidłowy wiek — wpisz liczbę");
    return;
}
// tu wiek jest już bezpieczną, poprawną liczbą

Pułapka: separator dziesiętny zależy od ustawień regionalnych

double.Parse("19,99"); // w Polsce (kultura pl-PL): 19.99 ✅
double.Parse("19.99"); // w Polsce: ❌ FormatException — przecinek to separator dziesiętny, kropka to nic

Parse bez dodatkowego parametru używa bieżącej kultury systemu, na którym działa aplikacja. Serwer skonfigurowany na en-US oczekuje kropki; serwer na pl-PL oczekuje przecinka. Kod, który działa poprawnie na Twoim laptopie, może się wywalić na serwerze produkcyjnym z inną kulturą systemową — klasyczny bug “u mnie działało”. Rozwiązanie: dla danych, które MUSZĄ być parsowane spójnie niezależnie od serwera (API, pliki konfiguracyjne, dane z bazy), zawsze podawaj CultureInfo jawnie:

double.Parse("19.99", CultureInfo.InvariantCulture); // zawsze kropka, niezależnie od serwera

CultureInfo.InvariantCulture to stały, niezależny od regionu format — używaj go wszędzie tam, gdzie format liczby pochodzi z systemu (API, JSON, XML), a CurrentCulture (domyślne zachowanie Parse) tylko tam, gdzie faktycznie chcesz dopasować się do ustawień regionalnych użytkownika (formularz w UI wyświetlany Polakowi).

Pułapka: przepełnienie przy rzutowaniu między typami całkowitymi milczy domyślnie

int duzaLiczba = 300;
byte maly = (byte)duzaLiczba; // byte mieści 0-255... i co teraz?

Console.WriteLine(maly); // 44 — nie wyjątek, tylko "obcięcie" bitów (300 - 256 = 44)

Rzutowanie między typami całkowitymi, gdzie wartość nie mieści się w typie docelowym, domyślnie nie rzuca wyjątku — po cichu obcina najstarsze bity, dając wynik, który wygląda jak poprawna liczba, ale nią nie jest. To szczególnie niebezpieczne w kodzie liczącym pieniądze albo indeksy. Blok checked wymusza wyjątek zamiast cichego obcięcia:

checked
{
    byte maly = (byte)duzaLiczba; // ✅ OverflowException zamiast cichego 44
}

Domyślne zachowanie C# to unchecked (szybsze, ale ryzykowne) — świadomie używaj checked w miejscach, gdzie przepełnienie oznacza realny błąd danych, nie “działanie zgodnie z projektem” (jak w hashowaniu, gdzie zawijanie bitów bywa zamierzone).

Rzutowanie obiektów: (Type) kontra as

object dane = "tekst";

string s1 = (string)dane;      // rzutowanie — rzuci InvalidCastException, jeśli się nie uda
string? s2 = dane as string;   // operator 'as' — zwróci null zamiast wyjątku, jeśli się nie uda

object liczba = 42;
string? s3 = liczba as string; // null — 42 (int) to nie string, ale bez wyjątku

as działa tylko dla typów referencyjnych (i Nullable<T>) i zamiast wyjątku zwraca null przy niepowodzeniu — wygodne, gdy chcesz sprawdzić typ bez obsługi wyjątku, ale wymaga potem sprawdzenia null zamiast try/catch. Nowoczesna alternatywa, często czytelniejsza:

if (dane is string tekst)
{
    Console.WriteLine(tekst.ToUpper()); // 'tekst' jest już rzutowany, gotowy do użycia
}

Wzorzec is z deklaracją zmiennej łączy sprawdzenie typu i rzutowanie w jednym kroku — bez ryzyka wyjątku i bez osobnej zmiennej nullable do sprawdzania.

Podsumowanie

Cztery narzędzia, cztery różne zastosowania: rzutowanie (int) do konwersji między znanymi typami liczbowymi (pamiętaj — obcina, nie zaokrągla), TryParse do danych z zewnątrz, których poprawności nie jesteś pewien, Parse do danych, które mają prawo rzucić wyjątek, jeśli są złe, i Convert tam, gdzie świadomie chcesz zachowania “zaokrąglij i nie wywalaj się na null” — co bywa zarówno zaletą, jak i ukrytą pułapką. Największy, najczęściej pomijany szczegół: zawsze podawaj CultureInfo.InvariantCulture przy parsowaniu liczb z danych, które nie pochodzą od użytkownika w UI — inaczej Twój kod “działa u mnie” i wybucha na serwerze z inną kulturą systemową.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →