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).
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.
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.