C# how to convert a string to int
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#.
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.