C# nigdy sam nie konwertuje między niezwiązanymi typami. String zawierający cyfry to nadal string, a double nie staje się po cichu int. Konwertujesz jawnie, a narzędzie zależy od tego, co konwertujesz: tekst na liczbę (parsowanie), jeden typ liczbowy na inny (rzutowanie) albo obiekt na bardziej szczegółowy typ (as, is).
String na int: Parse, TryParse i Convert
Są trzy standardowe sposoby zamiany tekstu na int:
Wynik:
2500
TryParse ok: 1250
TryParse failed, d = 0
42
-17
int.TryParse zwraca bool i zapisuje wynik w zmiennej out, która przy niepowodzeniu dostaje 0. Ponieważ złe dane są normalne we wszystkim, co wpisuje użytkownik, TryParse to domyślny wybór dla danych wejściowych, plików i parametrów zapytań. Te same metody istnieją w każdym typie liczbowym: long.TryParse, double.TryParse, decimal.Parse i tak dalej.
Co każda z nich robi ze złymi danymi
Trzy metody różnią się tylko sposobem, w jaki zawodzą:
Wynik:
int.Parse(null) throws ArgumentNullException
Convert.ToInt32(null) = 0
int.Parse("abc") throws FormatException
int.Parse("12.5") throws FormatException
int.Parse("1,000") throws FormatException
int.Parse("9999999999") throws OverflowException
int.Parse("") throws FormatException
FormatException niesie dobrze znany komunikat "Input string was not in a correct format" (w .NET 8 i nowszych "The input string '12.5' was not in a correct format", z tekstem w cudzysłowie) i to jeden z najczęstszych błędów czasu działania w C#: "12.5" nie jest int, tak samo jak "1,000". Żeby je akceptować, parsuj jako decimal albo powiedz parserowi, które style są dozwolone:
Wynik:
1000
255
13
10
Kultura: dlaczego "1.5" może stać się 15
Parsowanie domyślnie używa bieżącej kultury, a kultury różnią się separatorami. W niemieckiej i kilku innych europejskich kulturach (także w polskiej) separatorem dziesiętnym jest przecinek, a w niemieckiej kropka grupuje tysiące. Ten sam string daje różne liczby:
Wynik:
1.5
15
1.5
Kod, który czyta pliki z danymi, JSON, konfigurację albo cokolwiek generowanego maszynowo, powinien zawsze przekazywać CultureInfo.InvariantCulture, zarówno przy parsowaniu, jak i przy formatowaniu. Inaczej ten sam program działa na komputerze programisty, a na serwerze ustawionym na inny język źle odczytuje każdą liczbę. Bieżącej kultury używaj tylko dla tekstu, który człowiek wpisał albo przeczyta.
Niejawne i jawne konwersje liczbowe
Konwersje rozszerzające zachodzą niejawnie: int na long, int na double, float na double, int na decimal. Konwersja, która może stracić wielkość wartości albo jej część ułamkową, wymaga jawnego rzutowania (type)value. Jeden wyjątek, o którym warto pamiętać: int lub long na float oraz long na double są niejawne, choć mogą tracić precyzję: float f = 16_777_217; się kompiluje i zapisuje 16777216.
Wynik:
9 -9
10
2
4
705032704
9.99
7 7.0 7
Trzy rzeczy do zapamiętania. Rzutowanie z liczby zmiennoprzecinkowej na całkowitą obcina; nie zaokrągla. Convert.ToInt32(double) zaokrągla, stosując zaokrąglanie bankierskie (połówki idą do parzystego sąsiada). A rzutowanie całkowite, które się nie mieści, zawija się po cichu, chyba że działa w kontekście checked, gdzie rzuca OverflowException (zobacz typy danych).
int x = 3.7; to błąd kompilacji (CS0266: cannot implicitly convert type 'double' to 'int', an explicit conversion exists), czyli kompilator prosi cię o wybór między rzutowaniem a metodą zaokrąglania.
Liczby i inne typy na string
Każdy typ ma ToString(), a typy liczbowe przyjmują string formatu. Interpolacja stringów wywołuje to samo formatowanie:
Wynik:
42
000042
1234.50
1,234.50
$1,234.50
FF
1010
True
Order 000042: 1234.50
Kody formatu (D, F, N, C, X, P, E) i własne wzorce, takie jak "0.00", omawia strona interpolacja stringów.
Znaki i wartości logiczne
Wynik:
7
55
7
7
A
True
False
1
False
(int)'7' to 55, kod znaku, a nie 7. Potyka się o to każdy, kto sumuje cyfry liczby; odejmij '0' albo użyj char.GetNumericValue. bool.Parse przyjmuje "true" i "false" w dowolnej wielkości liter i nic poza tym ("yes" i "1" rzucają wyjątek).
Konwersje referencyjne: rzutowania, as i is
Dla typów klas rzutowanie sprawdza w czasie działania rzeczywisty typ obiektu. Rzutowanie w górę na klasę bazową jest niejawne. Rzutowanie w dół na klasę pochodną wymaga jawnego rzutowania, które rzuca InvalidCastException, gdy obiekt jest czymś innym:
Wynik:
4242
True
Card ending 4242, 50.00
InvalidCastException
5
cannot unbox int as long
Wybieraj is ze zmienną, gdy musisz rozgałęzić kod według typu; to jedno sprawdzenie, nie może rzucić wyjątku, a nowa zmienna jest w zakresie tylko tam, gdzie test się powiódł. Używaj zwykłego rzutowania, gdy inny typ byłby błędem, który powinien głośno się ujawnić. Przypadek opakowanego int zaskakuje osoby czytające liczby z kolekcji object albo komórek DataRow: najpierw rozpakuj do dokładnego typu ((long)(int)boxed) albo użyj Convert.ToInt64(boxed), który obsługuje każdy typ liczbowy.
Najczęściej zadawane pytania
Jak zamienić string na int w C#?
Użyj int.TryParse(text, out int number), gdy tekst pochodzi od użytkownika, z pliku albo z sieci: zwraca false zamiast rzucać wyjątek, gdy tekst nie jest poprawną liczbą. Użyj int.Parse(text), gdy niepoprawny tekst to błąd w kodzie, a wyjątek jest właściwą reakcją. Convert.ToInt32(text) działa jak int.Parse, tyle że dla null zwraca 0.
Jaka jest różnica między int.Parse a Convert.ToInt32?
Dla stringów są prawie takie same: obie rzucają FormatException przy tekście typu "abc" i OverflowException przy liczbach spoza zakresu. Różnica to null: int.Parse(null) rzuca ArgumentNullException, a Convert.ToInt32(null) zwraca 0. Convert.ToInt32 przyjmuje też inne typy (double, bool, object) i zaokrągla double do najbliższej parzystej liczby całkowitej zamiast obcinać.
Co oznacza "Input string was not in a correct format"?
To komunikat wyjątku FormatException rzucanego przez int.Parse, double.Parse albo Convert.ToInt32 (w .NET 8 i nowszych brzmi "The input string 'abc' was not in a correct format" i cytuje tekst), gdy tekst nie jest liczbą w oczekiwanym formacie: zawiera litery, jest pusty, ma separator dziesiętny przy parsowaniu int albo separator tysięcy. Przejdź na TryParse, żeby obsłużyć złe dane bez wyjątku, i sprawdź, w jakiej kulturze zapisano liczbę.
Jak rzutować double na int w C#?
Napisz jawne rzutowanie: int n = (int)price;. Rzutowanie obcina w stronę zera, więc (int)9.99 to 9, a (int)-9.99 to -9. Żeby zamiast tego zaokrąglić, użyj (int)Math.Round(price) i pamiętaj, że Math.Round domyślnie zaokrągla połówki do najbliższej liczby parzystej; przekaż MidpointRounding.AwayFromZero, żeby zaokrąglać jak w szkole.
Jaka jest różnica między as a rzutowaniem w C#?
Rzutowanie (Dog)animal rzuca InvalidCastException, jeśli obiekt nie jest Dog. animal as Dog zwraca wtedy null, więc po nim musi być sprawdzenie null, i działa tylko z typami referencyjnymi i dopuszczającymi null. Współczesną alternatywą jest wzorzec is: if (animal is Dog dog) { ... } sprawdza i konwertuje w jednym kroku.