[C#] Type Conversions and Casting

It is a very common scenario that you need to convert one data type to another. C# provides a lot of language constructs to convert data types from one to another and to verify the conversion’s validity.         

 

1. Implicit Casting – Widening data type

This is the easiest way of converting types. If the target data type is big enough to hold the original data type, you do not need to do anything. The compiler will widen the type automatically.

int i = 10;
long l = i; // from int to long - no problem

 

2. Explicit Casting – Narrowing data type

If you try to narrow the data type, the compiler will complain about the possible loss of data. You will get a compile error.

long l = 10;
int i = l; // error, even though integer can hold 10.

In this case, you need to specifically let the compiler know that you know this action might cause a data loss but you really need this conversion.

long l = 10;
int i = (int)l; // it's ok now.

Not all explicit castings work.

string val = "44";
int i = (int)val; // Compile error
int i = 0;
bool b = (bool)i; // Compile error

You cannot convert non-numeric types to numeric types just by casting.

 

3. Conversions from other types to strings

All types (even reference types) have a “ToString()” method to convert its data to the string representation. When you create a custom class, it is very common to override the “ToString()” method to provide the appropriate interpretation of the class rather than to use the default implementation (by default, ToString() returns the name of the type).

public class TypeFun
{
}
public class TypeFun1
{
  public override string ToString()
  {
    return "This is my Type";
  }
}
TypeFun fun = new TypeFun();
System.Console.WriteLine(fun.ToString()); // "TypeFun"
TypeFun1 fun1 = new TypeFun1();
System.Console.WriteLine(fun1.ToString()); // "This is my Type"

 

4. Conversions from Strings to other types

All wrapper classes (or structs) of primitive data types provide “Parse()” and “TryParse()” methods. For example, System.Int32 defines the following 2 methods:

  • public static int Parse(string s)
  • public static bool TryParse(string s, out int result)

Two methods are doing the same task. The only difference is that how they react when the string is not a valid number format and cannot be converted to a number; the “Parse()” will throw an Exception (FormatException) and the “TryParse()” will return false without throwing an Exception.

string val = "Fifty Five";
int number = 0;
try
{
  number = Int32.Parse(val);
}
catch (Exception e)
{
  Console.WriteLine(e.GetType().Name + ", " + e.Message);
}

if (Int32.TryParse(val, out number))
{
  Console.WriteLine($"Conversion succeeds: the value is {number}");
}
else
{
  Console.WriteLine($"Cannot convert {val} to an integer");
}

The output is:

FormatException, Input string was not in a correct format.
Cannot convert Fifty Five to an integer

 

5. Conversion between Boolean types and other types

Unlike C or C++, int and bool are not interchangeable.

  • From numbers to bool: not defined
  • From bool to numbers: not defined
  • From bool to string: Use the “ToString()” method defined in the “Boolean” struct type
  • From string to bool: Use the static methods “Parse()” and “TryParse()” of the “Boolean” type

The “Boolean” type also defines 2 properties to represent 2 statuses of the boolean type:

// public static readonly string FalseString;
// public static readonly string TrueString;

Console.WriteLine(Boolean.TrueString);
Console.WriteLine(Boolean.FalseString);

bool b = false;

// From bool to string
Console.WriteLine(b.ToString());

// from string to bool
if (Boolean.TryParse("True", out b))
{
  Console.WriteLine("Conversion succeeds: {0}", b);
}
else
{
  Console.WriteLine("Cannot convert True to a bool");
}

try
{
  b = Boolean.Parse("1"); // cannot convert 1 to a bool
}
catch (Exception e)
{
  Console.WriteLine(e.GetType().Name + ", " + e.Message);
}

True
False
False
Conversion succeeds: True
FormatException, String was not recognized as a valid Boolean.

 

6. Checked and Unchecked keywords

The basic rule of the “implicit” and “explicit” conversion is the range of the data types. If the target type is big enough to hold the original type, the conversion can be implicit. Otherwise, you need to explicitly cast the big type to the small type.

But how about the overflow of the value? Let’s see the following example.

int i = Int32.MaxValue;
Console.WriteLine("i = {0}", i);

int iOverflow =i+10;
Console.WriteLine("iOverflow = {0}", iOverflow);

The output is:

i = 2147483647
iOverflow = -2147483639

You expect iOverflow holds the value 2147483657, but the value is a lot different. Int32 type cannot hold more than 2147483647, therefore iOverflow cannot hold the value.

Now you notice the weirdness, right? There’s no error; no compile errors and even no run time errors. It just throws away the top most bits and assigns the wrong value. You can’t complain about this because it is the default behavior to handle the overflow in C#.

In most cases, you do not care the overflow situations and are fine with the default behavior. But in some situations, you want to handle the overflow in your way. Here the “checked” keyword is your rescue. If you use the “checked” keyword, the runtime will throw an “OverflowException” and you can catch it.

There are several places you can place the “checked” keyword.

  • inside the statement

int iOverflow = checked(Int32.MaxValue+10);

  • checked scope

checked
{
  int iOverflow = Int32.MaxValue+10;
}

  • project-wide

Project’s property page -> click the Advanced button on the Build tab. In the “Advanced Build Settings” dialog, check the “Check for arithmetic overflow/underflow” check box.

checked option

The usage of the “unchecked” keyword is the same as the “checked“. You use the “checked” keyword because the default project setting is the “unchecked“. So you can change the project setting as a “checked” and use the “unchecked” keyword whenever necessary.

 

7. Casting between a base class and a derived class

It is safe to store a reference to the derived type within a base class variable. This type of cast is implicit and automatic. But the cast from a base class type to a sub class type is not automatic. You need to cast specifically.

class Animal {}
class Lion : Animal {}

Animal myAnimal = new Lion(); // OK. from Lion to Amimal
Lion myLion = myAnimal; // Error, myAnimal is actually a Lion object
Lion myLion = (Lion)myAnimal; // OK

 

8. Determining the class hierarchy

Explicit casting of reference types might fail when the underlying type cannot be converted to a target type. In such a case a runtime exception will occur and you need to catch the exception with a try/catch block.

To make your life a little bit easier, C# provides the “as” keyword. You can convert reference types using the “as” keyword, which will return null upon failure instead of throwing an exception.

class Animal {}
class Lion : Animal {}
class Fruit {}

Animal myAnimal = new Lion();
Fruit myFruit = myAnimal as Fruit;

if ( myFruit == null ) { ... }

C# also provides the “is” keyword to check the compatibility of types in the class hierachy. The “is ” expression returns true or false.

class Animal {}
class Lion : Animal {}

Animal myAnimal = new Lion();
if ( myAnimal is Lion )
{
  Lion myLion = myAnimal as Lion;
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s