Often, our applications depend on external data sources that provide the string representations of .NET types. We have an option of using the Parse and TryParse methods to convert these string representations back to the base .NET types. 

The strings can be of any type ranging from numeric, date and time, boolean, chars, or enums. However, let’s focus on parsing numeric strings using Parse() and TryParse() in this article.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

The Parse() Method

We use the Parse() method to convert a string representation of a number to its numerical value. It returns the converted numerical value if the conversion is successful. The Parse() method throws an exception if the conversion fails. The exception can be:

  • an ArgumentNullException, if the input string is null
  • a FormatException, if the input string is of incorrect format
  • an OverFlowException, if the converted number exceeds the minimum or maximum range of the specified numeric type

Let’s look at the Parse() method overloads. 

Parse(String)

We are most likely to encounter this overload of the Parse() method. It converts a string representation of a number to its numerical value.

For the Parse() method to work, we need to pass a valid string. A valid input string would be a sequence of digits from 0 to 9. Optionally, we can also have a leading and trailing whitespace along with a leading sign (+/-).

Let’s demonstrate this with an example of the Parse(String) method from the System.Int32 class:

Assert.AreEqual(456, int.Parse("456"));
Assert.AreEqual(-4567, int.Parse("-4567"));
Assert.ThrowsException<FormatException>(() => int.Parse("3456.89"));
Assert.ThrowsException<OverflowException>(() => int.Parse("34343454574745"));
Assert.ThrowsException<ArgumentNullException>(() => int.Parse(null));

Parse(String, IFormatProvider)

This overload of the Parse() method converts a string representation of a number that is in a culture-specific format to its numerical value.

Let’s consider a culture where we denote positive numbers by a leading # sign. Hence, in this culture, the number 1234 would become #1234. However, #1234 is not a valid number in other cultures and would fail to convert. In such a scenario, we specify the culture using Parse(String, IFormatProvider):

var culture = new CultureInfo("en-US");
culture.NumberFormat.PositiveSign = "#";

Assert.AreEqual(1234, int.Parse("#1234", culture));
Assert.ThrowsException<FormatException>(() => int.Parse("#4567"));
Assert.ThrowsException<FormatException>(() => int.Parse("$4561", culture));
Assert.ThrowsException<OverflowException>(() => int.Parse("#34343454574745", culture));

A valid input string for Parse(String, IFormatProvider) is a sequence of digits from 0 to 9 with optional leading and trailing spaces along with a leading sign. 

Parse(String, NumberStyles)

This overload of the Parse() method converts a string to its numerical value based on the specified style of the number

We use the NumberStyles enum to specify the style elements such as separator symbols or exponential digits etc.

The combination of NumberStyles flags affect what a valid input string is. Along with the sequence of digits from 0 to 9, it may also contain leading and trailing signs, thousand operators, etc.:

Assert.AreEqual(3476, int.Parse("3476", NumberStyles.None));
Assert.AreEqual(76678, int.Parse("76678.0", NumberStyles.AllowDecimalPoint));
Assert.AreEqual(766780, int.Parse("766,780", NumberStyles.AllowThousands));
Assert.ThrowsException<FormatException>(() => int.Parse("$45,618", NumberStyles.AllowThousands));
Assert.ThrowsException<OverflowException>(() => int.Parse("56.89", NumberStyles.AllowDecimalPoint));

Parse(String, NumberStyles, IFormatProvider)

This overload of the Parse() method combines the former two overloads. We use it to convert a number’s string representation in a culture-specific format to its numerical value based on a specified style:

Assert.AreEqual(78, 
    int.Parse("78,000", 
        NumberStyles.Float | NumberStyles.AllowThousands, 
        new CultureInfo("fr-FR")));

Assert.AreEqual(78000,
    int.Parse("78,000",
        NumberStyles.AllowThousands, 
        new CultureInfo("en-GB")));

Assert.AreEqual(78, 
    int.Parse("78.000", 
        NumberStyles.Float, 
        new CultureInfo("en-US")));

Assert.ThrowsException<FormatException>(() => 
    int.Parse("$78,000", NumberStyles.Float, new CultureInfo("en-US")));

Assert.ThrowsException<OverflowException>(() => 
    int.Parse("78.567", NumberStyles.AllowDecimalPoint, new CultureInfo("en-US")));

Parse(ReadOnlySpan<Char>, NumberStyles, IFormatProvider)

This is a relatively newer overload of the Parse() method. It’s available in .NET versions Core 2.1 and above. We use it to convert a span of characters in a culture-specific format to a numerical value based on a specified style: 

Assert.AreEqual(78, 
    int.Parse("78,000".AsSpan(),
        NumberStyles.Float | NumberStyles.AllowThousands, 
        new CultureInfo("fr-FR")));

Assert.AreEqual(78000, 
    int.Parse("78,000".AsSpan(), 
        NumberStyles.AllowThousands, 
        new CultureInfo("en-GB")));

Assert.AreEqual(78, 
    int.Parse("78.000".AsSpan(), NumberStyles.Float, new CultureInfo("en-US")));

Assert.ThrowsException<FormatException>(() => 
    int.Parse("$78,000".AsSpan(), NumberStyles.Float, new CultureInfo("en-US")));

Assert.ThrowsException<OverflowException>(() => 
    int.Parse("78.567".AsSpan(), NumberStyles.AllowDecimalPoint, new CultureInfo("en-US")));

Thus, we conclude all the overloads of the Parse() method. Let’s turn our attention to the TryParse() method.

The TryParse() Method

The TryParse() method helps us avoid exceptions when parsing. Here, as a return value, we get a boolean flag indicating whether the conversion was successful. The last parameter contains the result of the conversion and is preceded by the out keyword.

In case of an unsuccessful conversion, the out parameter contains the default value of the specified type.

Because there is no exception handling involved, the TryParse() method performs better than Parse().

Let’s see which TryParse() overloads are available to us.

TryParse(String, Int32)

This is the most commonly used overload of the TryParse() method. We use it to convert a number’s string representation to its numerical value

The System.Int32 parameter contains the resulting numerical value if the conversion is successful or a zero in case of failure.

Let’s take the same example we did for Parse(String) and convert it to its TryParse() counterpart:

Assert.IsTrue(int.TryParse("45689", out int result));
Assert.AreEqual(45689, result);
Assert.IsFalse(int.TryParse("3456.89", out int formatNum));
Assert.IsFalse(int.TryParse("34343454574745", out int overflowNum));
Assert.IsFalse(int.TryParse(null, out int nullNum));

So, with TryParse() instead of throwing exceptions or coding by exceptions, we can use the returned bool flag to control our code flow.

TryParse(String, NumberStyles, IFormatProvider, Int32)

This overload of TryParse() is similar to Parse(String, NumberStyles, IFormatProvider). We use it to convert a string representation of a number in a culture-specific format to its numerical value based on the specified style.

Let’s continue using our examples for Parse(String, NumberStyles, IFormatProvider) to understand the difference:

Assert.IsTrue(int.TryParse("78,000", 
            NumberStyles.Float | NumberStyles.AllowThousands, 
            new CultureInfo("fr-FR"), 
            out int frNum));

Assert.AreEqual(78, frNum);

Assert.IsTrue(int.TryParse("78,000", 
            NumberStyles.AllowThousands, 
            new CultureInfo("en-GB"), 
            out int gbNum));

Assert.AreEqual(78000, gbNum);

Assert.IsTrue(int.TryParse("78.000", 
            NumberStyles.Float, 
            new CultureInfo("en-US"), out int usNum));

Assert.AreEqual(78, usNum);

Assert.IsFalse(int.TryParse("$78,000", 
            NumberStyles.Float, 
            new CultureInfo("en-US"), 
            out int floatNum));

Assert.IsFalse(int.TryParse("78.567", 
            NumberStyles.AllowDecimalPoint, 
            new CultureInfo("en-US"), 
            out int decimalNum));

TryParse(ReadOnlySpan<Char>, Int32)

We use this overload of the TryParse() method to convert a number’s span representation to its numerical value

This works similar to the TryParse(String, Int32) with the difference being ReadOnlySpan<Char> instead of String as the input parameter:

Assert.IsTrue(int.TryParse("45689".AsSpan(), out int result));
Assert.AreEqual(45689, result);

ReadOnlySpan<char> value = null;
Assert.IsFalse(int.TryParse(value, out int nullNum));
Assert.IsFalse(int.TryParse("3456.89".AsSpan(), out int formatNum));
Assert.IsFalse(int.TryParse("34343454574745".AsSpan(), out int overflowNum));

TryParse(ReadOnlySpan<Char>, NumberStyles, IFormatProvider, Int32)

This overload of TryParse() is similar to TryParse(String, NumberStyles, IFormatProvider, Int32). However, we pass ReadOnlySpan<Char> instead of a String here. 

We use it to convert a span representation of a number in a specified style and culture-specific format to its numerical value.

Hence, we can simply convert the method used for TryParse(String, NumberStyles, IFormatProvider, Int32) like:

Assert.IsTrue(int.TryParse("78,000".AsSpan(), 
            NumberStyles.Float | NumberStyles.AllowThousands, 
            new CultureInfo("fr-FR"), out int frNum));

Assert.AreEqual(78, frNum);

Assert.IsTrue(int.TryParse("78,000".AsSpan(),
            NumberStyles.AllowThousands, 
            new CultureInfo("en-GB"), out int gbNum));

Assert.AreEqual(78000, gbNum);

Assert.IsTrue(int.TryParse("78.000".AsSpan(), 
            NumberStyles.Float, 
            new CultureInfo("en-US"), 
            out int usNum));

Assert.AreEqual(78, usNum);

Assert.IsFalse(int.TryParse("$78,000".AsSpan(),
            NumberStyles.Float, 
            new CultureInfo("en-US"),
            out int floatNum));

Assert.IsFalse(int.TryParse("78.567".AsSpan(), 
            NumberStyles.AllowDecimalPoint, 
            new CultureInfo("en-US"), 
            out int decimalNum));

Parsing Other Numeric Strings

We’ve learned about parsing numeric strings using Parse() and TryParse(). However, the examples focus on System.Int32.

What about the other numeric types?

All the other numeric types have their respective Parse() and TryParse() methods with overloads similar to System.Int32.

So instead of an int.Parse(), we would use long.Parse(), double.Parse(), or a decimal.Parse() depending on whether the string representation (or span representation when applicable) is of a System.Int64, System.Double, or a System.Decimal number respectively.

The same is applicable for TryParse with long.TryParse(), double.TryParse(), decimal.TryParse() etc.

Conclusion

In the article, we learned about how the Parse and TryParse in C# work and their different overloads.

Parse() is useful in the scenarios where we care about the type of exception that can occur during failure to convert a string to its numerical value. Whereas, with TryParse() we get a better alternative in terms of performance and reliability by not having to deal with exception handling.

Hence, in cases where we don’t need the exact details of the exceptions, it’s almost always better to go with TryParse().