A Beginner's Tutorial - Type Casting and Type Conversion in C#
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 the use of variables. We can create a 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 variable.
From the compiler's perspective, assigning the 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 differs from type to type. But as developers, 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 into the library that provide most of the frequently needed casting operations.
Using the Code
Let us look at the 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:
The built-in numeric types can be assigned to each other if a narrow type is being assigned to a 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 a 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 a 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, we 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 okay for built-in numeric types, but in the 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 an integer is not possible.
Such casting expressions will compile successfully, but they will fail at runtime. What the C# compiler does is check whether these two types are cast compatible or not, and if not, it raises an InvalidCastException.
Recommended by LinkedIn
'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 with the type being casted to and returns a boolean value. So the exception-safe way to perform an explicit cast using the is operator would be:
static void Main(string[] args)
{
// CASE 1 *****
// This will work fine as o1 is actually an int
object o1 = 1;
int i = (int)o1;
// CASE 2 *****
// This won't work because o2 is not an int, so we need
// to have an is operator before the actual cast
object o2 = "1";
int j;
if (o2 is int)
{
j = (int)o2;
}
// CASE 3 *****
// We can never know what is the actual type of
// an object at runtime, so it's always better to use
// is operator, rewriting the first case
object o3 = 1;
int k;
if (o3 is int)
{
k = (int)o3;
}
}
Case 1 in the above code snippet will throw an exception if o1 is assigned to some type that is not an int, while 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 the is operator. Case 3 in the above code snippet will work fine, but it involves accessing the object twice: once for checking the compatibility (is operator) and then to actually extract the value (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 compatibility, and if it's okay, it will perform the cast too. If the cast is not compatible or unsuccessful, 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)
{
//use k for whatever we want to
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 others. 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 as implicit, the conversion will happen without needing an 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 a 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)
{
// since the rational equivalent of an int has 1 as denominator
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)
{
// Conversion from int to rational is implicit
Rational r1 = 23;
// Conversion from rational to double is explicit
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 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. The following code snippet shows how we can convert a string to an int using the Convert class:
static void Main(string[] args)
{
string s = "123";
try
{
int i = Convert.ToInt32(s);
}
catch (Exception ex)
{
// Pokemon exception handling, ideally rectification
// code and logging should be done here
}
}
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.
#CSharpProgramming #TypeCasting #DotNetTutorial #BeginnerFriendly