Our next stop in this series concludes our look at Casting by examining Downcasting.
Downcasting is the process by which we cast a reference of a parent class to class down to one of its derived class. This is the opposite of Upcasting, and once again we have deliberately avoided the use of the term Base Class because, frankly, it confuses matters. The whole concept of Downcasting from a Base doesn’t make sense from a lexical perspective.
So let’s get on with this and look at a Downcast:
B b = new B(); S s = b; //won't compile S s2 = (S)b;
The first thing to observe is we have to do this explicitly. Why? Because Downcasting forgoes the compiler’s ability enforce type safety. The compiler doesn’t like this – it makes it nervous, so we have to reassure it and promise that everything will be OK.
Unfortunately, our code above has broken that promise. The last of the above snippets S s2 = (S)b; compiles, but when we run the code, an InvalidCastException is thrown.
To perform a successful Downcast at runtime, the underlying object must be of the type we are Downcasting to. In other words we must have at some point have performed an Upcast on this variable, so think of this like a ‘back-downcast‘:
B b = new S(); //lines of code S s2 = (S)b;
But why even bother to Upcast if we are eventually going to Downcast?
Firstly, it is considered good Object-Oriented programming practice to Program To An Interface, Not An Implementation. Although all our examples have been Casting between a Parent class and its Derived classes, we could have been Casting between a class and an Interface that it implements. The Parent class could also have been some Abstract class that served a similar purpose (*).
Secondly, we might not be in control of the initial Cast. We could have sent our object into an API on a third-party library that required it to be Upcasted. When extracting this object back out of the API, it might have returned the reference in terms of the Parent class (or Interface). At this point we may have to Downcast it to get any meaningful use out of it.
The restrictions upon Downcasting and the desire to ensure type safety are important principles when trying to understand Variance. By knowing that we cannot perform an implicit Downcast we have a head start as we move to the next section.
We’ve now come to the end of our discussion on Casting. To be frank I’m slightly surprised that its taken up six posts, but hopefully its all been worthwhile. Armed with all our facts we can now move on into the mystifying and baffling world of Variance….
(*) For recommendation s of when to use Abstract Classes and when to use Interfaces, refer to the Microsoft guidelines on this subject: Choosing Between Classes and Interfaces.