C# how to convert a string to int

TOC

Time for a new post in my how-to series. In this series, I try to provide updated answers for common .NET/C# questions. I found that when googling common terms like "convert string to int", "write to a file", and similar, I would often get outdated StackOverflow answers, showing how to solve each problem with .NET 2 or even older. Even worse, most examples lack key aspects like exception handling and bad practices. For today's post, I will show you the best ways of converting strings to integers in C#.

C# how to convert a string to int

You have already tried converting a string to an int. Like parsing input from a text box in a system that didn't have modern model binding as we are used to today or when converting the output from a third-party API. While working for multiple companies both as a permanent and as a freelancer, I've seen a thousand lines of code, converting between data types. The most common pattern I've seen in C# is using the Parse-method:

var i = int.Parse("42");

While Parse provides a nice and simple interface for converting strings to int, it's rarely the right method to use. What happens if you provide something else than a number to the Parse-method:

var i = int.Parse("Oh no");

As expected, the Parse-method throws a FormatException. I cannot count the times I've seen this construct:

string s = "...";
int i = 0;
try
{
    i = int.Parse(s);
}
catch (FormatException)
{
}

Even documentation shows this approach as a valid way to parse ints. So, why is this a poor solution? Using exceptions as a control flow reduces the performance and makes your code harder to read. Luckily, .NET provides a much better way to parse ints without the need to catch exceptions: the TryParse-method:

string s = "...";
if (!int.TryParse(s, out int i))
{
    // Decide what to do since s cannot be parsed as an int
}

// Carry on

TryParse returns a boolean indicating if the parameter (s) was successfully parsed or not. If parsed, the value will go into the out parameter (i). Parsing with a default value on an invalid string is still dead simple:

int i = 0;
int.TryParse(s, out i);
// i is 0 if s could not be parsed as an int

Convert.ToInt32

There's a Convert class in the System namespace that you may be aquainted with. Convert offers a range of methods for converting one data type to another. For converting strings to ints, it's an abstraction on top of the Parse method from the previous section. This means that you will need to catch the FormatException to use the ToInt32-method:

string s = "...";
int i = 0;
try
{
    i = Convert.ToInt32(s);
}
catch (FormatException)
{
}

While it might be nice with a common abstraction for converting data types, I tend not to use the Convert class. Being forced to control flow using exceptions is something I always try to avoid, which (to my knowledge) isn't possible with the Convert class. Besides this, there are some additional things that you will need to be aware of when using the ToInt32-method. Take a look at the following code:

var i = Convert.ToInt32('1');
Console.WriteLine(i);

In the code, I'm converting the char 1 to an integer and writing it to the console. What do you expect the program to produce? The number 1, right? That's not the case, though. The overload of the ToInt32-method accepting a char as a parameter, converts the char to its UTF code, in this case 49. I've seen this go down a couple of times.

To summarize my opinion about the Convert-class in terms of converting strings to integers, stick to the TryParse-method instead.

Exception handling

As long as you use the TryParse-method, there's no need to catch any exceptions. Both the Parse and the ToInt32-method requires you to deal with exceptions:

try
{
    var result = int.Parse(s);
}
catch (ArgumentNullException)
{
    // s is null
}
catch (FormatException)
{
    // s is not a valid int
}
catch (OverflowException)
{
    // s is less than int.MinValue or more than int.MaxValue
}

Parsing complex strings

From my time working with financial systems, I was made aware of an overload of the TryParse-method that I don't see a lot of people using. The overload looks like this:

public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out Int32 result);

The parameter that I want to introduce you to is NumberStyles enum. The parameter accepts a bitwise combination of flags, allowing for more complex strings to be successfully parsed as integers. It's often much more readable to use this TryParse-overload, rather than doing a range of manipulations on the input string before parsing (like removing thousand separators, whitespaces, currency signs, etc.). Let's look at a couple of examples:

int.TryParse("(42)", System.Globalization.NumberStyles.AllowParentheses, null, out int result);

// result is -42

AllowParantheses will accept parantheses in the input string. But be aware that a parenthesized string is converted to a negative value. This format is often used in accounting and financial systems.

int.TryParse("$42", System.Globalization.NumberStyles.AllowCurrencySymbol, null, out int result);

// Result is 42

AllowCurrencySymbol will accept currency symbols inside the input string.

int.TryParse("42,000", System.Globalization.NumberStyles.AllowThousands, null, out int result);

// Result is 42000

AllowThousands will accept a thousand separator in the input string.

Like any bitwise flag, multiple NumberStyles can be combined:

int.TryParse(
    "($42,000)",
    System.Globalization.NumberStyles.AllowThousands |
    System.Globalization.NumberStyles.AllowParentheses |
    System.Globalization.NumberStyles.AllowCurrencySymbol,
    null,
    out int result);

// Result is -42000

Parsing anti-patterns

When looking at code, I often see different anti-patterns implemented around int parsing. Maybe someone copies code snippets from StackOverflow or blog posts with unnecessary code, who knows. This section is my attempt to debunk common myths.

Trimming for whitespace

This is probably the most common code example I've seen:

int.TryParse(" 42".Trim(), out int result);

By calling Trim the developer makes sure not to parse a string with whitespaces in the start and/or end. Trimming strings isn't nessecary, though. The TryParse-method automatically trims the input string.

Not using the TryParse overload

Another common anti-pattern is to do manual string manipulation to a string before sending it to the TryParse-method. Examples I've seen is to remove thousand separators, currency symbols, etc.:

int.TryParse("42,000".Replace(",", ""), out int result);

Like we've already seen, calling the TryParse-overload with the AllowThousands flag (or one of the others depending in the input string) is a better and more readable solution.

Control flow with exceptions

We already discussed this. But since this is a section of anti-patterns I want to repeat it. Control flow with exceptions slow down your code and make it less readable:

int result;
try
{
    result = int.Parse(s);
}
catch (FormatException)
{
    result = 42;
}

As I already mentioned, using the TryParse-method is a better solution here.

Avoid out parameters

There's a bit of a predicament when using the TryParse-method. Static code analysis tools often advise against using out parameters. I don't disagree there. out parameters allows for returning multiple values from a method which can violate the single responsibility principle. Whether you want to use the TryParse-method probably depends on how strict you want to be concerning the single responsibility principle.

If you want to, you can avoid the out parameter (or at least only use it once) by creating a small extension method:

public static class StringExtensions
{
    public static (int result, bool canParse) TryParse(this string s)
    {
        int res;
        var valid = int.TryParse(s, out res);
        return (result: res, canParse: valid);
    }
}

The method uses the built-in tuple support available in C# 7.

Convert to an array

A question that I often see is the option of parsing a list of integers in a string and converting it to an array or list of integers. With the knowledge already gained from this post, converting a string to an int array can be done easily with a bit of LINQ:

var s = "123";
var array = s
    .Split()
    .Select(s => Convert.ToInt32(s))
    .ToArray();

By splitting without parameters we get an enumerable of individual characters that can be converted to integers.

In most cases you would get a comma-separated list or similar. Parsing and converting that will require some arguments for the Split method:

var s = "1,2,3";
var array = s
    .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(s => Convert.ToInt32(s))
    .ToArray();

By splitting the string on comma (,) we get the individual characters in between.

When working with external APIs, developers come up with all sorts of weird constructs for representing null or other non-integer values. To make sure that you only parse integers, use the Char.IsNumber helper:

var s = "1,2,null,3";
var array = s
    .ToCharArray()
    .Where(c => Char.IsNumber(c))
    .Select(c => Convert.ToInt32(c.ToString()))
    .ToArray();

By only including characters which are numbers, we filter values like null from the input string.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website