Understanding Base Class/Derived Class Casting Rules
Now that you can build a family of related class types, you need to learn the laws of class type casting operations. To do so, let’s return to the Employees hierarchy created earlier in this chapter. Under the .NET platform, the ultimate base class in the system is System.Object. Therefore, everything “is-a” Object and can be treated as such. Given this fact, it is legal to store an instance of any type within an object variable:
// A Manager "is-a" System.Object.
object frank = new Manager("Frank Zappa", 9, 3000, 40000, "111-11-1111", 5);
In the Employees system, Managers, SalesPerson, and PTSalesPerson types all extend Employee, so we can store any of these objects in a valid base class reference. Therefore, the following statements are also legal:
// A Manager “is-an” Employee too.
Employee moonUnit = new Manager(“MoonUnit Zappa”, 2, 3001, 20000, “101-11-1321”, 1);
// A PTSalesPerson “is-a” SalesPerson.
SalesPerson jill = new PTSalesPerson(“Jill”, 834, 3002, 100000, “111-12-1119”, 90);
The first law of casting between class types is that when two classes are related by an “is-a” relationship, it is always safe to store a derived type within a base class reference. Formally, this is called an implicit cast, as “it just works” given the laws of inheritance. This leads to some powerful programming constructs. For example, assume you have defined a new method within your current Program class:
static void FireThisPerson(Employee emp)
{
// Remove from database...
// Get key and pencil sharpener from fired employee...
}
Because this method takes a single parameter of type Employee, you can effectively pass any descendent from the Employee class into this method directly, given the “is-a” relationship:
// Streamline the staff.
FireThisPerson(moonUnit); // "moonUnit" was declared as an Employee.
FireThisPerson(jill); // "jill" was declared as a SalesPerson.
The previous code compiles given the implicit cast from the base class type (Employee) to the derived type. However, what if you also wanted to fire Frank Zappa (currently stored in a generic System.Object reference)? If you pass the frank object directly into FireThisPerson() as follows:
// Error!
object frank = new Manager("Frank Zappa", 9, 3000, 40000, "111-11-1111", 5);
FireThisPerson(frank);
you will find a compiler error. As you can see, however, the object reference is pointing to an Employee-compatible object. You can satisfy the compiler by performing an explicit cast. This is the second law of casting: you must explicitly downcast using the C# casting operator. Thus, the previous problem can be avoided as follows:
// OK!
FireThisPerson((Manager)frank);
The C# as Keyword
Be very aware that explicit casting is evaluated at runtime, not compile time. Therefore, if you were to author the following C# code:
// Ack! You can't cast frank to a Hexagon!
Hexagon hex = (Hexagon)frank;
you would receive a runtime error, or more formally a runtime exception. Chapter 7 will examine the full details of structured exception handling; however, it is worth pointing out for the time being when you are performing an explicit cast, you can trap the possibility of an invalid cast using the try and catch keywords (again, don’t fret over the details):
// Catch a possible invalid cast.
try
{
Hexagon hex = (Hexagon)frank;
}
catch (InvalidCastException ex)
{
Console.WriteLine(ex.Message);
}
While this is a fine example of defensive programming, C# provides the as keyword to quickly determine at runtime whether a given type is compatible with another. When you use the as keyword, you are able to determine compatibility by checking against a null return value. Consider the following:
// Use 'as' to test compatability.
Hexagon hex2 = frank as Hexagon;
if (hex2 == null)
Console.WriteLine("Sorry, frank is not a Hexagon...");
The C# is Keyword
Given that the FireThisPerson() method has been designed to take any possible type derived from Employee, one question on your mind may be how this method can determine which derived type was sent into the method. On a related note, given that the incoming parameter is of type Employee, how can you gain access to the specialized members of the SalesPerson and Manager types?
In addition to the as keyword, the C# language provides the is keyword to determine whether two items are compatible. Unlike the as keyword, however, the is keyword returns false, rather than a null reference, if the types are incompatible. Consider the following implementation of the FireThisPerson() method:
static void FireThisPerson(Employee emp)
{
if (emp is SalesPerson)
{
Console.WriteLine("Lost a sales person named {0}", emp.Name);
Console.WriteLine("{0} made {1} sale(s)...", emp.Name,
((SalesPerson)emp).SalesNumber);
Console.WriteLine();
}
if (emp is Manager)
{
Console.WriteLine("Lost a suit named {0}", emp.Name);
Console.WriteLine("{0} had {1} stock options...", emp.Name,
((Manager)emp).StockOptions);
Console.WriteLine();
}
}
Here you are performing a runtime check to determine what the incoming base class reference is actually pointing to in memory. Once you determine whether you received a SalesPerson or Manager type, you are able to perform an explicit cast to gain access to the specialized members of the class. Also notice that you are not required to wrap your casting operations within a try/catch construct, as you know that the cast is safe if you enter either if scope, given our conditional check.

This excerpt was from Pro C# 2008 and the .NET 3.5 Platform, Fourth Edition by Andrew Troelsen.
