Introduction
As we know C# is a type-safe language that I have discussed in my previous
article. Type safety can be decided by the compiler at compile time or at run time by the CLR. I will explain here both of these types of type safety with some more examples and a small exercise in the end.
At runtime the CLR knows the type of an object. We can always discover the type of the object by calling the GetType() method. Since this method is non-virtual it is impossible for the type to spoof another type. Suppose we have an employee class as shown below.
public class Employee
{
}
public class Manager : Employee
{
}
public class SomeOtherType
{
}
Compile time type Checking
The preceding Manager class cannot override GetType() and return CEO as the class. There are basically two types of conversions that the compiler and CLR take care of in C#. They are implicit and explicit conversion. Implicit conversion takes place without any chances of data loss and that is why they are called safe explicit conversion.
Since I have defined a class Employee that implicitly derives from System.Object it is absolutely safe to create a variable of type object and assign it a reference of type Employee as shown below. In a more generalized term we can say that we can always have a variable of base type that can contain a reference of the derived type.
static void Main(string[] args)
{
Object obj = new Employee();
Employee emp = (Employee)obj;
SomeOtherType otherType = (SomeOtherType)emp;
}
Run time type Checking
There can be a scenario in which the compiler doesn't know the type of the object that it is casting to at compile time. The example of that is explained here.
I have a function defined as in the following.
private static void ChangeDesignation(object o)
{
Employee emp = (Employee)o;
object o refers to that allows the code to build, but at run time
}
At compile time the compiler knows that o is the type of object and we are trying to explicitly cast o to Employee. That is fine since Employee derives from Object. Now if I want to pass an instance of type Manager in the same function, as shown below, the execution would work fine.
static void Main(string[] args)
{
Manager manager = new Manager();
ChangeDesignation(manager);
only which is also safe
SomeOtherType otherType = new SomeOtherType();
ChangeDesignation(otherType);
}
Now in the next part of the code if I create an instance of SomeOtherType and pass as argument to the ChangeDesignation function the compiler would allow the code to compile but at run time we will get anInvalidCastException stating 'Unable to cast object of type 'TypeSafety.SomeOtherType' to type 'TypeSafety.Employee'.' since CLR knows that the SomeOtherType is not derived from the Employee class. This part of code was to show the Run Time Type checking. If the CLR would have allowed the cast, there would not have been any type safety and the result would have been unpredictable, including an application crash and security breaches caused by the ability of types to easily spoof other types.An easy solution to prevent this type of run time exception would have been to declare ChangeDesignation with Employee type as parameter type instead of object type so that the compiler produces a compile-time error. An object as a parameter type has been used here in this example to show the run time type checking.
Casting with the C# is and as Operators
Apart from explicit casting that we have used in the ChangeDesignation method, is to check the valid casting using the
is operator. The
is operator checks whether an object is compatible with a given type and the result of the evaluation is a Boolean, whether true or false. The
is operator never throws an exception. Kindly check the following code:
Object o = new Object();
Boolean b1 = (o is object);
Boolean b2 = (o is Employee);
If the object reference is null, the is operator always returns false since there is no object to check its type. The isoperator could have been typically used as in the following in the ChangeDesignation function:
if(o is Employee)
Employee emp = (Employee)o;
The CLR's type checking improves security, but it certainly comes at a performance cost, because the CLR must determine the actual type of the object referred to by the variable (o) and then the CLR must walk the inheritance hierarchy, checking each base type against the specified type (Employee). Since we need this kind of programming paradigm quite often, C# offers the as operator that simplifies our task and improves the performance bottleneck that is shown in the following code snippet:
Employee emp = o as Employee;
if(emp != null)
{
}
The as operator in the preceding code checks if the o is compatible with the Employee type and if it is, as returns a non-null reference to the same object. if o is not compatible, it simply returns null without throwing any exception. If we use the preceding code without checking for null, it can be dangerous as shown below:
Object o = new Object();
Employee e = o as Employee;
e.ToString();
Now I would like to discuss a very interesting exercise that we would perform here and see if the code is fine or we will get a Run Time Error (RTE) or Compile Time Error (CTE) using the two classes shown below.
public class Base
{ }
public class Derived: Base
{ }
static void Main(string[] args)
{
Object o1 = new Object();
Object o2 = new Base();
Object o3 = new Derived();
Object o4 = o3;
Base b1 = new Base();
Base b2 = new Derived();
Derived d1 = new Derived();
Base b3 = new Object();
Derived d2 = new object();
Base b4 = d1;
Derived d3 = b2;
Derived d4 = (Derived) d1;
Derived d5 = (Derived) b2;
Derived d6 = (Derived) b1;
Base b5 = (Base) o1;
Base b6 = (Derived) b2;
}