C# TryParse Usage: Parsing Strings Without Exceptions
Learn how to use TryParse in C# to convert strings to numbers safely without exceptions, compare it with Parse, and avoid common culture and validation pitfalls.
When a user enters a value into a form, the raw input arrives as a string. Converting that string to an integer, decimal, or other numeric type is a routine task, but the naive approach using int.Parse throws an exception when the input is not a valid number. The C# TryParse methods avoid that exception by returning a Boolean result and writing the parsed value to an out parameter. This article covers C# TryParse syntax, practical examples, common mistakes, and the tradeoffs compared with Parse.
How TryParse Works
The TryParse pattern is consistent across all numeric types in .NET. Each method takes a string (or a span of characters) and an out parameter of the target type. It returns true if parsing succeeded, and false if it failed. The out parameter is assigned the parsed value on success, or the default value of the type on failure.
bool success = int.TryParse(input, out int result);
If input is "42", success is true and result is 42. If input is "abc", success is false and result is 0. The method does not throw for invalid input, which makes it suitable for user input validation and configuration file parsing.
The same pattern exists for double, decimal, float, long, short, byte, and other numeric types. The out parameter can be declared inline or as a pre-declared variable.
Using TryParse with Numeric Types
The most common use case is converting a string to an integer. For example, reading a value from a text box or a query parameter:
string userInput = "123"; if (int.TryParse(userInput, out int number)) { Console.WriteLine($"Parsed number: {number}"); } else { Console.WriteLine("Invalid number"); }
For floating-point values, the behavior depends on the current culture. The double.TryParse and float.TryParse methods use the current culture by default, which means the decimal separator may be a comma in some locales. To ensure consistent behavior, pass an explicit culture, such as CultureInfo.InvariantCulture:
string value = "3.14"; if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out double pi)) { Console.WriteLine(pi); }
NumberStyles.Float permits leading and trailing whitespace, a sign, and a decimal point. CultureInfo.InvariantCulture makes parsing independent of the current locale.
The decimal type follows the same pattern and is often used for financial calculations where precision matters.
Comparing Parse and TryParse
The Parse method throws a FormatException when the input is invalid, and an OverflowException when the value is out of range. This forces you to wrap the call in a try-catch block to handle bad input. TryParse avoids that exception handling for invalid input.
try { int number = int.Parse(input); // use number } catch (FormatException) { // handle invalid input } catch (OverflowException) { // handle out of range }
The TryParse version is shorter and expresses the intent more clearly: you are attempting a conversion and checking whether it succeeded. It also avoids the overhead of exception construction, which is significant in tight loops or when parsing many values.
Handling Invalid Input Without Exceptions
A common pattern is to use TryParse as a validation step before using the parsed value. This is especially useful in console applications or web APIs where user input is unpredictable.
Console.Write("Enter age: "); string input = Console.ReadLine(); if (int.TryParse(input, out int age) && age >= 0) { Console.WriteLine($"Age: {age}"); } else { Console.WriteLine("Please enter a valid non-negative number."); }
The && condition ensures that parsing succeeded and the value meets additional constraints. This avoids nested if blocks and keeps the logic flat.
Common Mistakes with TryParse
One frequent mistake is ignoring the return value. Calling TryParse and then using the out variable without checking the result can lead to using a default value that is indistinguishable from a valid parsed zero. Always check the Boolean result before relying on the parsed value.
Another mistake is assuming that TryParse handles all whitespace. The default overloads trim leading and trailing whitespace, so " 42 " parses, but they do not remove internal spaces, so "4 2" fails. Clean or validate the input before calling TryParse if internal spaces are possible.
Culture issues are also common. The default int.TryParse overload uses the current culture for signs and whitespace, but it does not enable NumberStyles.AllowThousands, so a grouped value like "1,234" fails rather than becoming 1234. Floating-point overloads are more permissive by default and may accept group separators or culture-specific decimal separators. For machine-generated data, pass CultureInfo.InvariantCulture and an explicit NumberStyles value so the result does not depend on the user's locale.
Performance and Maintainability Considerations
The primary performance benefit of TryParse is avoiding exceptions. Exceptions are relatively expensive because they involve stack unwinding and object allocation. In a loop that processes thousands of records, using TryParse instead of Parse inside a try-catch can reduce CPU usage and memory pressure.
From a maintainability perspective, TryParse makes the control flow explicit. The reader immediately sees that a conversion may fail and that the code handles both outcomes. This reduces the chance of unhandled exceptions in production and makes the code easier to test because you can pass invalid inputs without expecting exceptions.
Advanced Usage: Span-Based Overloads
In .NET Core 2.1 and later, the numeric TryParse methods have overloads that accept ReadOnlySpan<char> instead of a string. This is useful when parsing a substring without allocating a new string. For example, when processing a large string buffer, you can slice a span and parse it directly.
ReadOnlySpan<char> span = "12345".AsSpan(0, 3); if (int.TryParse(span, out int result)) { Console.WriteLine(result); // 123 }
This overload avoids the allocation of a substring, which can improve performance in high-throughput scenarios. Overloads are available for the common numeric types such as int, double, and decimal. When you need to parse a segment of a larger string, prefer the span-based version to reduce garbage collection pressure.