Introduction
In this short article which discusses type casting in C#, we will look into how implicit
type casting works, when we will need explicit
casts and how we can enhance our user defined types to support implicit
or explicit
casts.
Background
Programming in any language will involve manipulation of in-memory data. Accessing this in memory data is via use of variable. We can create specific type of variable to access/manipulate some data. C# allows us to create variables of many types but, being a statically typed language, it does not allow us to assign the value of one type of variable into another type of variables.
From the compiler's perspective, assigning value of one type of variable to another is perhaps not a valid operation (except for implicit casts). This also makes sense since the representation of actual data differ from type to type. But as a developer, we know and can understand the logical relationship and conversion between data types. In a lot of cases, it is almost inevitable to avoid this value assignment from one data type to another.
For the above mentioned reasons, C# has provided the possibility of casting one data type to another. Not only that, it also provides us the flexibility to take full control over casting in our hands (user defined conversions). Along with that, it has a lot of helper classes built in the library that provides most of the frequently needed casting operations.
Using the Code
Let us look at different ways of casting/conversion possible in C#.
Implicit Casts
Let us start by looking into the conversions that are done automatically by C#. Implicit casts are the casts that do not require the programmer to do an explicit conversion. One data type can simply be assigned to another. There are some rules that govern the implicit cast.
- Built-in numeric types, widening conversions
- Reference types, Derived class to base class
The built in numeric types can be assigned to each other if a narrow type is being assigned to wider type. This is possible because the compiler knows that the only problem in such operations is that a little more memory will be needed to hold this type and no data will be truncated or lost. So the following conversion will not need any explicit cast.
int i = 10;
long l = i;
Secondly, If we try to assign a value from derived class to a base class, it will work. That is because a derived class always is-a base class. Also, from a memory perspective, a base class variable pointing to a derived class object can safely access the base class part of the object in memory without any problem. So, the following code will work without needing any explicit casts.
class Base
{
}
class Derived : Base
{
}
class Program
{
static void Main(string[] args)
{
Derived d = new Derived();
Base b = d;
}
}
Other than these two possible scenarios, all the conversions will create compile time errors. Still, If we need to perform the conversions, we will have to use explicit casting/conversion.
Explicit Casts
If we find ourselves in need of conversion that is either narrowing conversion or conversion between unrelated types, then we will have to use explicit conversions. Using explicit conversions, we are actually letting the compiler know that we know there is possible information loss but still we need to make this conversion. So if we need to convert a long type to an integer type, we need to cast it explicitly.
long l = 10;
int i = (int)l;
On similar lines, if we need to cast a base class to a derived class, I will have to cast it explicitly.
Base b = new Base();
Derived d = (Derived)b;
Explicit casting actually tells the compiler that we know about possible information loss/mismatch but still we need to perform this cast. This is ok for inbuilt numeric types but in case of reference types, there is a possibility that the types are not at all compatible, i.e., casting from one type to another is not at all possible. For example, casting a string "abc
" to Integer is not possible.
Such casting expressions will compile successfully, but they will fail at run-time. What C# compiler does is that it checks whether these two types are cast compatible or not and if not, it raises an exception InvalidCastException
.
'is' and 'as' operators
So whenever we are using explicit casts, it is always a good idea to wrap the cast inside a try-catch
block. C# also provides is
and as
operators which are helpful in performing explicit casts in an exception safe manner.
The is
operator checks whether the type being casted from is compatible to the type being casted to and returns a boolean
value. So the exception safe way to perform an explicit cast using is
operator would be:
static void Main(string[] args)
{
object o1 = 1;
int i = (int)o1;
object o2 = "1";
int j;
if (o2 is int)
{
j = (int)o2;
}
object o3 = 1;
int k;
if (o3 is int)
{
k = (int)o3;
}
}
Case 1 in the above code snippet will throw an exception is o1
is assigned to some type that is not int
, the other two cases are exception safe and will only perform the cast if the types are compatible for casting.
There is one small performance issue in using is
operator. Case 3 in the above code snippet will work fine but it involves accessing the object twice. Once for checking the compatibility, i.e., the is
operator and secondly to actually extract out the value, i.e., casting. Can we not have something like - "Check the compatibility and if compatible, perform the cast" in one single operation. That is where the as
operator comes into the picture.
The as
operator checks the compatibility and if it's OK, it will perform the cast too. If the cast is not compatible or unsuccessful, then the result will be null
. So, the above cast can be rewritten using the as
operator:
object o3 = 1;
int? k = o3 as int?;
if (k != null)
{
Console.WriteLine(k.Value);
}
Note: The important thing to note here is that the as
operator can only be used with reference types. This is the reason we used nullable
int
in the above example.
So, if we need to have an exception safe casting, we can use is
or as
operator. We should use is
operator if the target type is a value type and as
operator if the target type is a reference type.
User Defined Conversions
C# also provides the flexibility for defining conversions on classes and structs so that they can be converted to and from other. The conversion operators simply contain the logic of how the conversion should happen. We can define these conversion operators to be implicit or explicit. If we define them implicit, the conversion will happen without needing as explicit cast. If we define it as explicit, the casting will be required.
Let us try to see how we can implement these conversion operations. Let us implement a small class Rational
to hold rational number. We will then define two conversion operations. Int
to Rational
(an implicit conversion) and Rational
to double
(an explicit conversion).
class Rational
{
int numerator;
int denominator;
public Rational(int num, int den)
{
numerator = num;
denominator = den;
}
public static implicit operator Rational(int i)
{
Rational rational = new Rational(i, 1);
return rational;
}
public static explicit operator double(Rational r)
{
double result = ((double)r.numerator) / r.denominator;
return result;
}
}
And now, let us see how we can use these conversion operators to perform actual conversions:
static void Main(string[] args)
{
Rational r1 = 23;
Rational r2 = new Rational(3, 2);
double d = (double)r2;
}
Before wrapping up, there is one more thing that beginners should be aware of. There are a lot of helper classes and helper functions available in C# to perform the frequently needed conversions. It is always a good idea to refer to the documentation to achieve the desired conversions before putting in the code for conversions. Following code snippet shows how we can convert string
to int
using Convert
class.
static void Main(string[] args)
{
string s = "123";
try
{
int i = Convert.ToInt32(s);
}
catch (Exception ex)
{
}
}
Points of Interest
Conversion from one type to another is inevitable in any programming language. Knowing the basics about conversions will let us use the right approach in the right scenario. This small article discussed the same about C#. This has been written for beginners so some of the experienced programmers could find this article futile. Nevertheless, I hope this has been informative.
History
- 27th August, 2012: First version