In this article, we are going to learn various ways of converting a value from one type to another (type casting) and how to check the type of a value at run time (type checking).

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

Let’s start.

Type Casting in C#

In any statically-typed language, a value has an assigned type that can’t be changed after its creation.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

However, sometimes we need to copy that value to another variable or use it in a method parameter that has a different type. In that situation, we can use several ways to coerce a value of type A into type B (type casting). 

Implicit conversions

When a type can be safely converted to another type without losing data, the compiler will let us assign the new value without any special syntax.

For instance, we can implicitly assign a numeric value to a variable whose type has a wider range of values than the initial type:

int number = 10;
long biggerNumber = number; // biggerNumber contains 10

For reference types, there’s always an implicit conversion to its direct or indirect base types:

Horse horse = new();   // derived type
Animal animal = horse; // assign to the base type

Cast Expressions

There are situations where a value may be converted to another type but there is a risk of data loss or the conversion may fail for other reasons. In these cases, there will be no implicit conversion in place and we’ll have to use a cast expression:

long biggerNumber = 10;
int number = (int)biggerNumber; // number contains 10

Cast expressions consist of using parenthesis to the left of an expression to enclose the type we want the result of the expression to be converted to. Be aware that, if the value overflows the destination type, you may get the wrong data or an overflow exception depending on your compiler configuration.

When working with reference types, we need to use cast expressions when converting a base type to a derived type:

Animal animal = new Horse(); // base type variable
Horse horse = (Horse)animal; // assign to derived type

Usually, the compiler tries to catch an invalid cast expression and throws a compile-time error. However, that’s not always possible so, in some cases, we can get an InvalidCastException:

Animal animal = new Monkey();
Horse horse = (Horse)animal; // compiles but throws InvalidCastException at run-time

The “as” Operator

We can use the as operator to perform explicit casts instead of the parentheses syntax. The difference is that the as operator doesn’t throw a run-time exception in case of an invalid cast. In that case, the conversion will simply result in a null value:

Animal animal = new Horse();

Horse? horse = animal as Horse;    // explicit conversion
Monkey? monkey = animal as Monkey; // invalid conversion, monkey contains null

We can use the as operator only with reference or nullable types. The as operator doesn’t consider user-defined conversions, for user-defined conversions use a cast expression.

Conversion Helpers

The Parse()  method will convert a string to another type provided the string has the correct format:

int number = int.Parse("10");      // convert string to integer
bool boolean = bool.Parse("True"); // convert string to boolean

This operation will throw a FormatException at runtime if the string doesn’t have the right format for the conversion.

We can use the TryParse() variant to avoid having to handle exceptions. TryParse() returns a bool value indicating whether the operation succeeded. The converted value will be stored in an output parameter:

var succeed = int.TryParse("10", out var number);
// succeed == true
// number == 10

The Convert class exposes many static type conversion methods between basic types:

decimal fraction = 12.5M;

int integer = Convert.ToInt16(fraction);
string str = Convert.ToString(fraction);
bool boolean = Convert.ToBoolean("true");

Finally, we can use the BitConverter class to convert basic types from and to their basic representation as byte arrays:

int integer = 1024;
float fraction = 12.5F;

var integerAsBytes = BitConverter.GetBytes(integer);
var floatAsBytes = BitConverter.GetBytes(fraction);

var format = "{0,12}{1,15}";
Console.WriteLine(format, "Base Type", "Bytes");
Console.WriteLine(format, "=========", "=====");
Console.WriteLine(format, integer, BitConverter.ToString(integerAsBytes));
Console.WriteLine(format, fraction, BitConverter.ToString(floatAsBytes));

// Base Type         Bytes
// =========         =====
//      1024   00-04-00-00
//      12.5   00-00-48-41

User-Defined Conversions

When creating our reference types we can implement implicit and explicit type conversions using operator overload syntax:

public class Currency
{
    private string _symbol;
    private decimal _value;

    public Currency(string symbol, decimal value)
    {
        _symbol = symbol;
        _value = value;
    }

    public static implicit operator decimal(Currency c) => c._value;
    public static explicit operator Currency(decimal d) => new Currency("$", d);

    public override string ToString() => $"{_symbol}{_value:0.00}";
}

Here, we are creating a custom Currency type and its implicit conversion to the decimal base type. Also, we are overloading the explicit cast operator to allow conversions from decimal to Currency using cast expressions:

var currency = new Currency("$", 1950.75M);
decimal decimalValue = currency; // decimalValue contains 1950.75

var anotherDecimal = 775.2M;
var convertedCurrency = (Currency)anotherDecimal; // convertedCurrency prints "$775.20"

Type Checking in C#

We will often find ourselves in situations where we need to determine whether a given value or instance belongs to a specific reference or base type, that’s what we call type checking.

In C#, the Type class represents a type at run-time. It is useful in many scenarios, type checking is one of them.

The typeof() Operator

The typeof() operator receives the name of a type as a parameter and returns the Type class instance for that type: 

var typeValue = typeof(string);

Also, we can use typeof() with type parameters:

string GetTypeName<T>() => typeof(T).Name;

var typeName = GetTypeName<int>();      // Int32
var typeName2 = GetTypeName<decimal>(); // Decimal

Here, we define a function GetTypeName<T>() that takes a type parameter T and uses typeof() to get the related Type instance. After that, we use the Name property of the Type class to return the type’s name to the caller. Note that the returned type names correspond to the boxed types.

The GetType() Method

We can call the Object.GetType() method on any instance or value at run-time to get its associated instance of the Type class:

var stringValue = "hello";
var singleValue = 12.5f;

stringValue.GetType(); // String
singleValue.GetType(); // Single

How to Type Check With typeof() And GetType()

Using both typeof() and Object.GetType() we can type check any value at run-time:

object[] values = { 6, 12.5f, 100M, true, "hello" };

foreach (var val in values)
{
    string? message;
    if (val.GetType().Equals(typeof(int)))
    {
        message = $"Value {val} is an integer";
    }
    else if (val.GetType().Equals(typeof(decimal)))
    {
        message = $"Value {val} is a decimal";
    }
    else if (val.GetType().Equals(typeof(float)))
    {
        message = $"Value {val} is a floating-point value";
    }
    else if (val.GetType().Equals(typeof(bool)))
    {
        message = $"Value {val} is a boolean";
    }
    else if (val.GetType().Equals(typeof(string)))
    {
        message = $"Value {val} is a string";
    }
    else
    {
        message = $"Value {val} belongs to an unknown type";
    }

    Console.WriteLine(message);
}

// Value 6 is an integer
// Value 12.5 is a floating-point value
// Value 100 is a decimal
// Value True is a boolean
// Value hello is a string

Here, we start with an array of values of different types. Next, we call the GetType() method on each value to get its associated run-time type definition. After that, we compare that type definition with the result of using the typeof() operator on different type names to determine whether a value is of a certain known type.

Note that we are using the Equals() method to compare Type instances. This way, we ensure that we are correctly comparing values and not references.

The “is” Operator

A more succinct and safe way to type check is to use the is operator. The is operator takes a value or expression to its left and a type identifier to its right and returns true if the type of the value on the left is compatible with the type on the right:

int number = 16;
string name = "John Smith";

number is int;  // returns true
name is string; // returns true
number is bool; // returns false

Starting with C# 7, it is possible to perform safe castings using the is operator and type patterns:

if (data is string name)
{
    message = $"Hello {name}!";
}

Let’s assume that the data variable is of type Object and we don’t know at run-time what kind of data will it contain. We use the is operator and the type pattern to conditionally create a local string variable that will contain the converted data. If the value in the data variable is not a string the conditional expression will resolve to false and the conversion will not take place.

Conclusion

In this article, we’ve learned how implicit and explicit type casting works. We’ve also learned about some conversion helper methods and how to implement our custom-type conversions.

Additionally, we’ve seen some examples of how to type-check value types at run-time and we’ve learned what as and is operators are and how to use them.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!