Use int.TryParse(text, out int n) when the text might not be a number: it returns true or false instead of throwing, and puts the value in n. When you know the text is valid, int.Parse("42") returns the int 42 and throws FormatException or OverflowException for anything else. Convert.ToInt32(text) parses the same way, except that it returns 0 for null.
C# won't cast a string to an int: (int)"42" is a compile error, Cannot convert type 'string' to 'int'. Parsing lives in static methods on int itself (int is an alias for System.Int32, so int.Parse and Int32.Parse are the same method). The choice that matters is what happens on bad input: an exception from int.Parse, a false from int.TryParse, or a quiet 0 from Convert.ToInt32(null). After that come the follow-ups: checking a string before parsing, hex and formatted numbers, and values that don't fit in 32 bits. Each example runs on this page: hit Run, then edit the code and run it again.
1int.TryParseRecommended
int.TryParse(s, out int n) is the everyday answer for text from users, files, config and query strings. It returns a bool and writes the parsed value to the out variable, so there is no exception to catch and no cost for bad input. Declaring the variable inside the call (out int n) needs C# 7 or later. It accepts null too, and simply returns false.
Output
"42" and "-15" parse, and so does " 7 ", because surrounding whitespace is allowed by default. Everything else prints not an int: empty text, letters, a decimal point, null, and "2147483648", which is a valid number one past int.MaxValue. On failure n is 0, so don't read it unless the call returned true. The setting "8o80" has a letter o in it and falls back to port: 8080. The int? helper prints 13 and True; writing ? n : null without a cast needs C# 9, and older code writes (int?)null.
2int.Parse and its exceptions
int.Parse(s) returns the int directly and throws when it can't. Use it for text that should always be valid, such as a value your own code wrote, where bad input is a bug you want to hear about. It accepts a leading - or +, and unlike Java's parseInt it skips leading and trailing spaces, tabs and newlines. There are three exceptions to know.
Output
Prints 50, then 428, which is the bug the conversion exists to prevent: + with a string on either side concatenates. The next lines print -17, 5, 7 and 42, so "007" is plain decimal and the newline is ignored. Bad text throws FormatException: The input string 'abc' was not in a correct format., and the same for "3.5" and "1,234" (recent .NET versions quote the input; older ones say Input string was not in a correct format.). A number that is too big throws OverflowException, Value was either too large or too small for an Int32., and null throws ArgumentNullException. To handle them all in one place, see catching multiple exceptions, though TryParse is usually simpler.
3int.Parse vs Convert.ToInt32
Convert.ToInt32(string) is a thin wrapper: it returns 0 for null and calls int.Parse for everything else. Its real value is in the other overloads. It takes an object, so it can convert a value from an object-typed API whatever its runtime type. It converts a double by rounding. And it reads base 2, 8 and 16 strings.
Output
Prints 42 and -7 (whitespace is skipped, as with int.Parse), then 0 for the null string where int.Parse throws ArgumentNullException. That 0 is the catch: a missing value and a real "0" come out the same. Empty text is still a FormatException for both. The object overload prints 7, while casting the same object fails with Unable to cast object of type 'System.String' to type 'System.Int32'., because a cast can unbox an int but never parses. The double line prints 2 4 3: Convert.ToInt32 rounds halves to the even neighbour, and a cast truncates. The base lines print 10, 255 (base 16 accepts a 0x prefix) and -1, because base 16 reads the 32 bits as two's complement. Any other base throws ArgumentException, and so does a minus sign outside base 10.
4Check if a string is a number
To ask "is this a number?" without keeping the value, call int.TryParse(s, out _) with a discard. The popular alternatives are a char.IsDigit check and a regex. The grid below runs all three on the same inputs so you can see where they disagree with the parser.
Output
TryParse is the only column that knows the range of an int: "99999999999" is all digits, and it still doesn't fit. The IsDigit check rejects "-7" and accepts the Devanagari "४२", because char.IsDigit is true for every Unicode decimal digit, while int.Parse only reads ASCII 0 to 9. The regex has the same \d problem, and its $ also matches before a final newline, so "42\n" passes. The stricter versions all print False: char.IsAsciiDigit (.NET 7+), [0-9] with \z, and TryParse with NumberStyles.AllowLeadingSign, which turns off the whitespace that the default style accepts. For decimals, double.TryParse says yes to more than digits: 3.5, 1000 for "1e3", 1234 for "1,234", NaN and -Infinity.
5Hex, thousands separators and other formats
int.Parse and int.TryParse both have overloads that take a NumberStyles and an IFormatProvider. The style says what is allowed (hex digits, group separators, parentheses, a currency symbol), and the culture says which characters those are. Without a culture, parsing uses the current culture of the machine, which is invariant in this sandbox but not on a German or French PC. For text that a program wrote, pass CultureInfo.InvariantCulture.
Output
The hex lines print 255, 2147483647 and -1, since FFFFFFFF is read as raw bits, and 31 once the 0x is stripped: NumberStyles.HexNumber throws FormatException on the prefix. The separator lines print 1234567 twice, then False, because the invariant culture's group separator is a comma. Then -42 for accounting-style parentheses, 1000, and 1234 from the currency string. NumberStyles.Number accepts "1,234.00" as 1234, but a real fraction throws OverflowException, not FormatException. The last line prints 1024: int.Parse also takes a ReadOnlySpan<char>, so it can read part of a string without allocating a substring, and the leading space is skipped as usual.
6Too big for an int, or a decimal string
An int holds up to 2147483647. For bigger whole numbers, such as IDs, file sizes and Unix times in milliseconds, use long.Parse or long.TryParse, which follow the same rules up to about 9.2 × 1018, or BigInteger.Parse from System.Numerics, which has no fixed limit. A string with a decimal point is a different job: parse it as a double or decimal first, then decide how it becomes whole.
Output
Prints 2147483647, 9000000000 and False. The plain cast prints 410065408: C# arithmetic is unchecked by default, so (int) keeps the low 32 bits without a word, while checked throws OverflowException: Arithmetic operation resulted in an overflow. The BigInteger adds exactly, 123456789012345678901234567891, and its explicit conversion to int always checks, even without checked. The decimal lines are 3 (the cast truncates), 4 (Math.Round) and -3, since truncation goes toward zero. Note that Math.Round rounds halves to even by default; pass MidpointRounding.AwayFromZero for school rounding. Finally decimal.IsInteger (.NET 7+) lets you reject a fraction instead of losing it: 42, then False for "3.99".
7Which should you use?
| Method | Returns | On bad input | Best for |
|---|---|---|---|
| int.TryParse(s, out int n) | bool, value in n | false, n = 0 | User input, files, config: the default |
| int.Parse(s) | int | Throws FormatException or OverflowException | Text that should always be valid |
| Convert.ToInt32(x) | int (0 for null) | Throws, except null gives 0 | object values, doubles, base 2, 8 or 16 |
| int.Parse(s, NumberStyles, culture) | int | Throws (TryParse has the same overload) | Hex, thousands separators, currency |
| Regex / char.IsAsciiDigit | bool | false, but misses overflow | Checking the shape of the text only |
| long.Parse(s) | long | Throws FormatException or OverflowException | Values past 2147483647 |
| BigInteger.Parse(s) | BigInteger | Throws FormatException | Integers of any size |
Frequently asked questions
What is the difference between int.Parse and Convert.ToInt32?
For a string, Convert.ToInt32(s) returns 0 when s is null and otherwise calls int.Parse(s), so both throw FormatException for "abc" and for an empty string, while int.Parse(null) throws ArgumentNullException. Convert.ToInt32 also has overloads for object, double (it rounds half to even, so 2.5 becomes 2) and a base argument (2, 8, 10 or 16). Use int.Parse or int.TryParse for strings, so that a missing value doesn't silently turn into 0.
Should I use int.Parse or int.TryParse?
Use int.TryParse whenever bad input is expected, which covers anything a person typed or a file contained: if (int.TryParse(s, out int n)) { ... }. It returns false instead of throwing, and exceptions are slow when a lot of input is bad. Use int.Parse when the text should always be valid, so a bad value surfaces as an exception instead of being skipped.
How do I check if a string is a number in C#?
Call int.TryParse(s, out _), or double.TryParse(s, CultureInfo.InvariantCulture, out _) for decimals. It is the only check that also rejects values outside the range of an int, such as "99999999999". Be careful with the shortcuts: s.All(char.IsDigit) rejects "-7" and accepts non-ASCII digits like "४२", and the regex ^\d+$ also accepts those digits and a trailing newline. If you only want ASCII digits, use char.IsAsciiDigit (.NET 7+) or ^[0-9]+\z.
Why can't I cast a string to an int with (int)?
A cast converts between numeric types or unboxes a value; it never parses text. int n = (int)"42"; fails to compile with error CS0030: Cannot convert type 'string' to 'int'. If the string is stored in an object, the cast compiles but throws InvalidCastException at run time. Use int.Parse, int.TryParse or Convert.ToInt32 instead.
How do I convert a hex string like "0xFF" to an int?
Convert.ToInt32("0x1F", 16) returns 31 and accepts the prefix. int.Parse("1F", NumberStyles.HexNumber, CultureInfo.InvariantCulture) also returns 31, but it throws FormatException if the 0x is still there, so strip it first. Both read eight hex digits as raw bits: "FFFFFFFF" gives -1, not 4294967295. Use uint.Parse or long.Parse with the same style if you want the unsigned value.
Why does int.Parse fail on "1,234" or on a number copied from a web page?
The default style allows only whitespace, a sign and digits, so "1,234" throws FormatException. Remove the separators with s.Replace(",", ""), or pass NumberStyles.AllowThousands with CultureInfo.InvariantCulture; that style doesn't check where the commas are, so "12,34" also gives 1234. Text from web pages often contains a non-breaking space (U+00A0), which the parser does not skip: int.TryParse("\u00A042", out _) is false. s.Trim() removes it, after which the parse returns 42.
Why does double.Parse("3.99") return 399 on some machines?
Parsing without a culture uses the current culture of the machine. In German, the comma is the decimal separator and the dot groups thousands, so double.Parse("3.99", new CultureInfo("de-DE")) returns 399 and "3,99" returns 3.99. The same applies to group separators in int.Parse. When the text was written by a program (JSON, CSV, config files), always pass CultureInfo.InvariantCulture; use the user's culture only for text the user typed.