As a statically typed language, C# imposes some limitations on variable types. After we declare a variable of a given type, we can only assign values of that type to it. For example, if we declare a variable of a textual type, we cannot assign values of a numerical type to it. There are, however, situations that this is exactly what we need. Fortunately, conversions from one type to another are possible. In this article, we will learn how to perform different types of conversions in C#.
So, let’s get started.
Types of Conversions in C#
In some cases, conversions even occur unnoticed. Such conversions are called implicit conversions. They occur when there is no data loss. This is the case, for example, when we convert from smaller to larger integral types or from derived classes to base classes. Implicit conversions occur automatically and they don’t require any special syntax.
Sometimes, however, we want to convert from larger integral types to smaller ones. Or, we want to convert to numeric types with less precision. Sometimes we need to convert doubles to ints, strings to dates, or booleans to strings. And we could go on and on like that. What all these conversions have in common, is that there may be some data loss or the types may be so incompatible that the conversions just fail. Fortunately, such conversions do not occur automatically and we must use special syntax. We call them explicit conversions.
Explicit conversions can be implemented in a couple of different ways. We can use a cast operator or special helper classes. The special classes include System.BitConverter
and System.Convert
. With numeric types, such as int
or double
, we can also use the Parse()
method.
If you want to learn more about type conversions, you should check out our article Type Conversions in C#.
In this article, we’ll focus on the Convert
class defined in the System
namespace.
The Convert Class
The Convert
class is a static class with methods to convert every base data type to every other base data type in .NET. It doesn’t mean all conversions are successful, which we are going to see later in the article. The base types supported by the class are Boolean, Char, SByte, Byte, Int16, Int32, Int64, UInt16, UInt32, UInt64, Single, Double, Decimal, DateTime, and String.
Besides base types, the class includes methods to perform other conversions. In particular, we can convert custom objects to base types, which we are also going to discuss in the article. The Convert
class also supports base64 encoding.
Let’s have a look at some examples to get the feel of the syntax.
Syntax
Each method in the Convert
class is overloaded. The ToDouble()
method, for example, has 18 overloaded versions. The ToString()
method has 36. Each version takes one or two parameters. The first parameter is always of the type we’re converting from. Some examples of the overloaded ToDouble()
method include:
Convert.ToDouble(bool value); Convert.ToDouble(char value); Convert.ToDouble(DateTime value); Convert.ToDouble(int value);
It works the same for the other methods. Later in the article, we’ll see overloads that take two parameters. For now, however, let’s stick to the most basic form.
We just saw the signatures of one of the methods. Now, let’s exercise a few of them:
public static string ConvertIntToOtherTypes(int value) { var intToDouble = Convert.ToDouble(value); var intToBool = Convert.ToBoolean(value); var intToString = Convert.ToString(value); var intToDoubleText = $"{value} ({value.GetType()}) -> {intToDouble} ({intToDouble.GetType()})"; var intToBoolText = $"{value} ({value.GetType()}) -> {intToBool} ({intToBool.GetType()})"; var intToStringText = $"{value} ({value.GetType()}) -> {intToString} ({intToString.GetType()})"; var output = $"{intToDoubleText}\n{intToBoolText}\n{intToStringText}"; return output; }
Here we’re using three methods, ToDouble()
, ToBoolean()
and ToString()
with one parameter of type int
. If we call the method with the parameter 4, it returns the values and types before and after the conversion:
4 (System.Int32) -> 4 (System.Double) 4 (System.Int32) -> True (System.Boolean) 4 (System.Int32) -> 4 (System.String)
These three conversions turned out successful, but this isn’t always the case.
Possible Conversion Outcomes
Types in C# are related to one another to a varying extent. For example, int
and short
are more related to each other than DateTime
and bool
. Because of this, not all conversions are equally feasible. Some conversions are pretty straightforward. Some are completely impossible. Yet other conversions are possible, but cause loss of information, so we must be careful with them.
In this article, we’re going to explore quite a few methods, so let’s create a convenient method to make our lives easier:
public static string MakeConversion<T1, T2>(Func<T1, T2> convert, T1 value) { try { T2 result = convert(value); var output = $"{value} ({value?.GetType()}) -> {result} ({result?.GetType()})"; if (value?.GetType() == result?.GetType()) { output += "\nNo conversion, same types"; } return output; } catch (Exception ex) { return ex.Message; } }
This is a generic method that takes two parameters. The first parameter is a delegate that will point to the method from the Convert
class that we want to test. The second argument is of the type we want to convert from. If the conversion is successful, the MakeConversion
method will return a message similar to the ones above. Otherwise, it will return an error message.
We’re going to use this method to get a clear and meaningful outcome:
Utilities.MakeConversion(Convert.ToString, true);
This calls the Convert.ToString(bool)
method and returns a message with the result of the conversion.
Let’s now explore all the possible conversion outcomes.
Successful Conversion
Conversions that work without any loss of data are always successful. As an example, let’s convert an int
to a long
. The resulting type has a wider range than the original one, so this is a widening conversion:
Utilities.MakeConversion(Convert.ToInt64, 1500);
Widening conversions always succeed:
1500 (System.Int32) -> 1500 (System.Int64)
In the opposite direction, though, the conversion will be a narrowing one, because int
has a narrower range than long
:
Utilities.MakeConversion(Convert.ToInt32, 1500L);
This conversion is only successful because we’re using a long
type that is less than the maximum value of int
:
1500 (System.Int64) -> 1500 (System.Int32)
But what happens if we try to convert a long
that is beyond the range of the int
type? Let’s find out.
Overflow Exception
An OverflowException
is thrown if we try to convert a type with a wider range to a type with a narrower range. A conversion from a large long
to an int
is a good example:
Utilities.MakeConversion(Convert.ToInt32, 10_000_000_000L);
Such conversions are impossible because they would result in a loss of data:
Value was either too large or too small for an Int32.
The two types, long
and int
, differ in range, but they are both numeric types. Sometimes, however, the types we’re trying to convert between are completely incompatible. Let’s see what happens then.
Invalid Cast Exception
Some conversions are just not supported. For example, we can’t convert from char
to bool
, float
, double
, decimal
or DateTime
. We can’t convert in the opposite direction, either. Let’s see what happens if we try to convert from char
to bool
:
Utilities.MakeConversion(Convert.ToBoolean, 'a');
An InvalidCastException
is thrown:
Invalid cast from 'Char' to 'Boolean'.
All conversions from and to DateTime
result in an InvalidCastException
, unless the other type is a string
.
Speaking of strings, we can convert any type to string
. But does it always work the other way around? Let’s check it out.
Format Exception
It turns out that we can only convert strings to other types if they are formatted correctly. For example, we can’t convert string
to char
if the former consists of more than a single character:
Utilities.MakeConversion(Convert.ToChar, "hey");
This results in a FormatException
:
String must be exactly one character long.
Also, we can’t convert string
to a numeric type if it contains invalid characters:
Utilities.MakeConversion(Convert.ToInt32, "125s");
We get a clear message:
The input string '125s' was not in a correct format.
We also get a FormatException
if we try to convert string
to bool
. This conversion is only successful if the string
equals Boolean.TrueString
or Boolean.FalseString
using StringComparison.OrdinalIgnoreCase
.
Also, just like with numeric types, we can’t convert string
to DateTime
if its format is incorrect.
In the examples above, we either successfully converted from one type to another or the conversion failed. But sometimes there is no conversion at all.
No Conversion
Let’s try to convert a type to itself:
Utilities.MakeConversion(Convert.ToInt32, 10);
Here, we’re trying to convert int
to int
. In such cases, there is no conversion, and the original type is returned:
10 (System.Int32) -> 10 (System.Int32) No conversion, same types
We’ve exhausted all the possible conversion outcomes. We saw that not all conversions are possible. Sometimes there would be a loss of data. This is the case with numeric types. Let’s discuss this aspect in more detail.
Loss of Data and Loss of Precision
We know that conversions with a loss of data throw an OverflowException
. This is what happens, for example, if we try to convert a very large double
to a float
. This isn’t the case, however, if there is only a loss of precision:
Utilities.MakeConversion(Convert.ToSingle, 246.123456789);
As double
type supports more digits of precision than float
, some of the least significant digits are lost:
246.123456789 (System.Double) -> 246.12346 (System.Single)
The last digit of the resulting float
is rounded.
We also lose precision when we convert from a floating-point type to an integral type. Let’s try to convert float
to int
:
MakeConversion(Convert.ToInt32, 56.91f);
We lose all digits after the decimal point and the number is rounded:
56.91 (System.Single) -> 57 (System.Int32)
Here we saw some examples of conversions between numbers. There are many numeric types and the Convert
class supports a great number of conversions between them. Let’s have a closer look at some examples.
Conversions Between Numeric Types
The Convert
class supports conversions between any two numeric types, including signed and unsigned types:
Utilities.MakeConversion(Convert.ToByte, 125); Utilities.MakeConversion(Convert.ToDecimal, 125); Utilities.MakeConversion(Convert.ToByte, 125u); Utilities.MakeConversion(Convert.ToInt16, 125); Utilities.MakeConversion(Convert.ToInt32, 125u); Utilities.MakeConversion(Convert.ToSByte, 125L); Utilities.MakeConversion(Convert.ToUInt32, 125);
All the conversions are successful. This is because the number 125 is small enough to fit in the range of all the types used in the example:
125 (System.Int32) -> 125 (System.Byte) 125 (System.Int32) -> 125 (System.Decimal) 125 (System.UInt32) -> 125 (System.Byte) 125 (System.Int32) -> 125 (System.Int16) 125 (System.UInt32) -> 125 (System.Int32) 125 (System.Int64) -> 125 (System.SByte) 125 (System.Int32) -> 125 (System.UInt32)
We must remember, though, that with larger numbers the OverflowException
may be thrown.
Conversions between numeric types are very common. No less common are conversions to strings.
Conversions to Strings
Let’s convert some common types to a string
:
Utilities.MakeConversion(Convert.ToString, true); Utilities.MakeConversion(Convert.ToString, 4.56f); Utilities.MakeConversion(Convert.ToString, 'c'); Utilities.MakeConversion(Convert.ToString, new DateTime()); Utilities.MakeConversion(Convert.ToString, new Random());
All these conversions are successful:
True (System.Boolean) -> True (System.String) 4.56 (System.Single) -> 4.56 (System.String) c (System.Char) -> c (System.String) 1/1/0001 12:00:00 AM (System.DateTime) -> 1/1/0001 12:00:00 AM (System.String) System.Random (System.Random) -> System.Random (System.String)
In the last example, we’re converting an object of type Random
to string
. The result of this conversion is the type information as a string
.
We can convert any object
to string
, even an object we define ourselves. We can also convert binary data to string
. Let’s have a look at how this works.
Base64 Encoding
We can use the Convert
class for base64 encoding. There are methods to convert an array of bytes to and from string
. We can also convert to and from an array of base-64 Unicode characters. As an example let’s take the ToBase64String()
method. It converts an array of 8-bit unsigned integers to its string
representation encoded as base-64 digits:
byte[] bytes = [5, 10, 15, 20, 25, 30]; Utilities.MakeConversion(Convert.ToBase64String, bytes);
This example outputs a base-64 string
:
System.Byte[] (System.Byte[]) -> BQoPFBke (System.String)
We’re not going to discuss this topic in detail here. If you want to read more, check out our article Base64 Encode and Decode in C#.
We’ve seen examples of conversions between strings and numbers. But these don’t have to be decimal numbers.
Conversions With Non-decimal Numbers
In the examples above we’ve been using only decimal integral numbers. What about binary, octal, and hexadecimal numbers? Well, some methods enable us to convert integral values to their non-decimal string
representations. There are also methods to convert non-decimal string
representations to integral values. Let’s start with the first group of methods.
Conversions to Non-decimal String Representations
The methods converting integral values to non-decimal string
representations take two parameters. The first parameter is the integral number we want to convert. It may be of any signed integral type (byte
, short
, int
, long
). The second parameter is the base. The base is always of type int
and its value is 2, 8, 10, or 16. For example, to convert the number 17 to its binary string
representation, we pass the integer and base 2 as arguments:
Convert.ToString(17, 2);
The method returns the binary string
representation of the number. To make it clear, let’s create a new method:
public static string ConvertToStringWithBase(int number, int baseValue) { try { var numberRepresentation = Convert.ToString(number, baseValue); return $"{number} (in base 10) -> {numberRepresentation} (in base {baseValue})"; } catch { return "Wrong base (must be 2, 8, 10 or 16)"; } }
This method just calls the ToString(number, baseValue)
method and returns a meaningful message. Let’s try it out with the four allowed bases and an incorrect one:
Utilities.ConvertToStringWithBase(100, 2); Utilities.ConvertToStringWithBase(100, 8); Utilities.ConvertToStringWithBase(100, 10); Utilities.ConvertToStringWithBase(100, 16); Utilities.ConvertToStringWithBase(100, 3);
We get the string
representations of the number 100 in base 2, base 8, base 10, and base 16. The last conversion is impossible due to an incorrect base:
100 (in base 10) -> 1100100 (in base 2) 100 (in base 10) -> 144 (in base 8) 100 (in base 10) -> 100 (in base 10) 100 (in base 10) -> 64 (in base 16) Wrong base (must be 2, 8, 10 or 16)
Conversions from Non-decimal String Representations
We can also convert in the opposite direction, so from non-decimal string
representations to numbers. To do that, we use methods like ToByte()
, ToInt16()
, ToInt32()
or ToInt64()
. Signed bytes and unsigned integers are also supported. For the latter, we should use methods like ToUInt16()
and so on.
These methods take two parameters, too. The first parameter is the string
representation of the number. The second parameter is the base. Again, let’s create a convenient method to present the outcome in a readable way:
public static string ConvertFromStringWithBase(string numberRepresentation, int baseValue) { try { var number = Convert.ToInt32(numberRepresentation, baseValue); return $"{numberRepresentation} (in base {baseValue}) -> {number} (in base 10)"; } catch { return "Wrong base (must be 2, 8, 10 or 16)"; } }
And let’s see some examples:
Utilities.ConvertFromStringWithBase("1010", 2); Utilities.ConvertFromStringWithBase("34", 8); Utilities.ConvertFromStringWithBase("51", 10); Utilities.ConvertFromStringWithBase("3A", 16); Utilities.ConvertFromStringWithBase("65", 5);
We get the integral values of the string
representations. Naturally, there is an incorrect base in the last example:
1010 (in base 2) -> 10 (in base 10) 34 (in base 8) -> 28 (in base 10) 51 (in base 10) -> 51 (in base 10) 3A (in base 16) -> 58 (in base 10) Wrong base (must be 2, 8, 10 or 16)
We’ve seen quite a few conversions between numbers and strings. But there are lots of other conversions, like conversions to booleans. Let’s have a look at them next.
Conversions to Booleans
The Convert
class supports many conversions to booleans, although not all are supported. The exceptions are char
and DateTime
. Conversions between these two types always result in an InvalidCastException
.
Conversions from numeric types to booleans work fine. If the value is zero, it’s converted to false
. Otherwise, it’s converted to true
:
Utilities.MakeConversion(Convert.ToBoolean, 5); Utilities.MakeConversion(Convert.ToBoolean, 0); Utilities.MakeConversion(Convert.ToBoolean, -19.875); Utilities.MakeConversion(Convert.ToBoolean, 4.65f); Utilities.MakeConversion(Convert.ToBoolean, 170.54m);
All numbers in the examples above are converted to true
except the second one:
5 (System.Int32) -> True (System.Boolean) 0 (System.Int32) -> False (System.Boolean) -19.875 (System.Double) -> True (System.Boolean) 4.65 (System.Single) -> True (System.Boolean) 170.54 (System.Decimal) -> True (System.Boolean)
Apart from numeric types, we can convert objects that implement the IConvertible
interface to bool
. We’re going to discuss such objects in a moment.
We can also convert strings that contain the value of TrueString
or FalseString
:
Utilities.MakeConversion(Convert.ToBoolean, "True"); Utilities.MakeConversion(Convert.ToBoolean, "false");
The string
representations of the logical values can start with an uppercase or lowercase letter:
True (System.String) -> True (System.Boolean) false (System.String) -> False (System.Boolean)
We know how to convert numeric types to booleans. But we can also convert in the opposite direction. And there are lots of other interesting conversions between numeric and non-numeric types.
Conversions Between Numeric and Non-numeric Types
Conversions between numeric and non-numeric types are pretty common. We’ve already seen examples of such conversions. Let’s have a look at some more. In particular, let’s explore conversions between numeric types on one side and booleans, strings and chars on the other.
Conversions from Booleans to Numeric Types
So, we can convert booleans to integers:
Utilities.MakeConversion(Convert.ToInt32, true); Utilities.MakeConversion(Convert.ToInt32, false);
false
is converted to 0, true
to 1:
True (System.Boolean) -> 1 (System.Int32) False (System.Boolean) -> 0 (System.Int32)
This conversion works the same for any other integral or floating-point numeric type.
Conversions from Strings to Numeric Types
If a string
is formatted correctly, we can convert it to a numeric type. Let’s check out a conversion to int
and another to double
:
Utilities.MakeConversion(Convert.ToInt32, "128"); Utilities.MakeConversion(Convert.ToDouble, "1.83e3");
They both work as expected:
128 (System.String) -> 128 (System.Int32) 1.83e3 (System.String) -> 1830 (System.Double)
It’s also possible to convert strings to other numeric types, provided they’re correctly formatted.
Conversions between Numeric Types and Unicode Characters
We can convert chars to integral types and integral types to chars. It’s not possible to convert between chars and non-integral types.
We use the ToChar()
method to convert an integer to its corresponding Unicode character:
Utilities.MakeConversion(Convert.ToChar, 65); Utilities.MakeConversion(Convert.ToChar, 41);
In these two examples, we pass an int
to the method, so we’re converting a 32-bit signed integer to its corresponding Unicode character:
65 (System.Int32) -> A (System.Char) 41 (System.Int32) -> ) (System.Char)
We can also pass other integral types, like long
, byte
, or ushort
, to mention just a few.
To convert a Unicode character to its corresponding integer, we use methods like ToInt32()
, ToInt64()
or ToUInt16()
and so on:
Utilities.MakeConversion(Convert.ToInt32, '&');
This particular Unicode character corresponds to the value 38:
& (System.Char) -> 38 (System.Int32)
Using the ToInt32()
method, we get a 32-bit signed integer. Using the other methods, we would get other integral types.
The examples above demonstrate that conversions between numeric and non-numeric types are pretty common. Earlier examples show that conversions to strings and booleans are not uncommon. But there are types with a very limited number of possible conversions. An example is DateTime
. Let’s have a look at possible conversions with this type.
DateTime Conversions
Using the Conversion
class we can convert the DateTime
type only to a string
, and that’s it. Similarly, we can only convert to DateTime
from a string
. All other conversions result in an InvalidCastException
.
Conversion from DateTime
to string
is pretty straightforward:
Utilities.MakeConversion(Convert.ToString, new DateTime(2023, 11, 27, 9, 45, 28));
What we get is the string
representation of the date and time:
11/27/2023 9:45:28 AM (System.DateTime) -> 11/27/2023 9:45:28 AM (System.String)
For a conversion in the opposite direction to work, the string
must be in the correct format:
Utilities.MakeConversion(Convert.ToDateTime, "8/2/2003 12:00:00 AM"); Utilities.MakeConversion(Convert.ToDateTime, "02 June 1985 3:20:17 AM");
We can see two possible string
formats. The conversion to DateTime
works just fine:
8/2/2003 12:00:00 AM (System.String) -> 8/2/2003 12:00:00 AM (System.DateTime) 02 June 1985 3:20:17 AM (System.String) -> 6/2/1985 3:20:17 AM (System.DateTime)
The way we write dates and times varies from country to country. Actually, not just dates and times, but even numbers have different formatting. To address this issue, we can add culture-specific formatting information to the methods defined in the Convert
class. Let’s have a look at how to do that.
Culture-Specific Formatting Information
Sometimes it’s necessary to use culture-specific formatting. This is when the IFormatProvider
interface comes in handy. All base-type conversion methods have overloaded versions that take two parameters. The first parameter is of the type we’re converting from. The second parameter is of type IFormatProvider
:
Convert.ToDateTime(String, IFormatProvider)
However, in most cases, the second parameter is ignored. It’s useful only when we convert to DateTime
or numeric types.
Converting to DateTime with IFormatProvider
For the next two examples, we first need to set the default thread culture to en-US
to ensure the current culture for our application is set to US. If you are already running under a US culture, you can skip this step:
var culture = new CultureInfo("en-US"); CultureInfo.DefaultThreadCurrentCulture = culture; CultureInfo.DefaultThreadCurrentUICulture = culture;
With that step behind us, we are ready to jump into the examples.
Let’s start by converting a string
representation of a date defined under the Austrian (de-AT) culture to a DateTime
:
CultureInfo cultureDE = new("de-AT"); string deDate = "15/2/2021"; Console.WriteLine( $"Austrian culture: {deDate} --- US culture: {Convert.ToDateTime(deDate, cultureDE).ToShortDateString()}");
The difference between the US and Austrian cultures is that the month and the day of the date are in a different order:
Austrian culture: 15/2/2021 --- US culture: 2/15/2021
Converting to Numeric Types with IFormatProvider
Numeric formats across cultures differ too. For example, we use different decimal separators in the USA and Austria:
string deNumber = "280,99"; Console.WriteLine( $"Austrian culture: {deNumber} --- US culture: {Convert.ToDecimal(deNumber, cultureDE)}");
The American culture uses a point as the decimal separator, whereas the Austrian culture uses a comma:
Austrian culture: 280,99 --- US culture: 280.99
Although there are so many conversion methods, they don’t exhaust all the possibilities. What if we want to convert a custom object to a base type? Well, this is also possible.
Conversions from Custom Objects to Base Types
The Convert
class contains methods to convert between base types. But we can also convert from any custom type to any base type. This is possible if the custom type implements the IConvertible interface. This interface defines methods that convert the implementing type to other types, such as ToByte()
, ToChar()
, ToInt32()
, etc. If a particular conversion is not supported, it should throw an InvalidCastException
.
To see that in action, let’s define a custom type that implements the interface. The Note
class can be used to represent a musical note. In our example, we can convert a Note
object to any base type except DateTime
:
public class Note : IConvertible { private readonly char _tone; private static readonly char[] _tones = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; public Note(char tone) => _tone = tone; public bool ToBoolean(IFormatProvider? provider) => _tones.Contains(_tone); public char ToChar(IFormatProvider? provider) => _tone; public DateTime ToDateTime(IFormatProvider? provider) { throw new InvalidCastException(); } public long ToInt64(IFormatProvider? provider) => Convert.ToInt64(Array.FindIndex(_tones, t => t == _tone) + 1); public string ToString(IFormatProvider? provider) => _tone.ToString(); ... }
This is not the complete implementation of the interface. But it will do for our purposes.
We can now convert Note
to string
, bool
, int
, long
, double
, and so on:
Note note = new('C'); Utilities.MakeConversion(Convert.ToBoolean, note); Utilities.MakeConversion(Convert.ToInt64, note); Utilities.MakeConversion(Convert.ToChar, note);
Here we’re defining a Note
object that represents the C tone. Let’s examine the conversion:
ConversionsInCSharp.Note (ConversionsInCSharp.Note) -> True (System.Boolean) ConversionsInCSharp.Note (ConversionsInCSharp.Note) -> 3 (System.Int64) ConversionsInCSharp.Note (ConversionsInCSharp.Note) -> C (System.Char)
If we look at the implementation of the methods in the Note
class, we see that the conversions work as expected.
Conclusion
In this article, we’ve covered different types of conversions. We took a look at the different options for conversion that exist within C#. We learned about both implicit and explicit conversions. From there we moved on to exploring the Convert
class, which enables us to convert any base type to any other base type. It also supports converting from custom types to base types.
The relations between types vary. Some are closely related, like the numeric types, and thus relatively easy to convert. Others, not so closely related, are a bit more challenging and may be impossible to convert. The main thing we have to remember when converting is that conversions sometimes result in a loss of data or precision and may be affected by culture-specific information.