Deep C# - Casting the Escape from Strong Typing |
Written by Mike James | |||||
Page 3 of 4
Downcasting works in the same way, but it isn’t always safe. Its type safety depends on the class being referenced actually being of the correct type, and it is again like a narrowing cast for value types but the analogy is not exact. For example, after: MyClassA myA = (MyClassA) new MyClassB(); it is obvious that myA is actually referencing a MyClassB object, but the rules of strong typing means that you can only use the methods defined in MyClassA. Suppose you know that myA is referencing a MyClassB object and want to use its methods, what can you do? The answer is you can downcast to MyClassB as in: MyClassB myB= (MyClassB) myA; and now you can use myB as if it referenced a MyClassB object – because that is what it is. If you want to avoid having to involve another variable and just want to use MyClassB methods, you can write the cast as: ((ClassB) myA).MyMethodB(); The extra parentheses are necessary because of the precedence of the operations. You can also make use of the as operator: (myA as MyClassB).MyMethodB(); A downcast is not always safe. If myA turns out to be a type that isn’t a MyClassB object or derive from MyClassB then a run-time error occurs. Downcasting can be dangerous if the object isn’t the type you think it is
Downcasting, moving down the inheritance hierarchy, isn’t safe Generic AlgorithmsHaving made clear what up- and downcasting are, why do we need them? What downcasting allows you to do is to use a reference to a class without really knowing exactly what it is. The most extreme example of downcasting is to use an object reference for any class you want to work with, for example: object MyAnything = new MyClassB(); ((ClassB)MyAnything).methodB(); Why might this be useful? The answer is that you can now write code that will work with any type using nothing but object references. In short, you can write generic algorithms. For example, if we define a sort method that works with objects: public object[] MySort(object[] array) { //do the sort return array; } then, if we declare an array of custom objects: MyClass[] testdata = new MyClass[10]; we can sort the array using: MyClass[] resultdata = (MyClass[]) MySort((object[])testdata); There is an upcast to object [] in the call and a downcast to MyClass[] in the return – and this is the first time we notice the connection between input parameters and upcasting and outputs and downcasting. This really does mean that the method can sort any type we care to throw at it – assuming it knows how to order the type, i.e. to know when MyClass[i] is greater or smaller than MyClass[j]. You can usually provide an order relation to a class by giving it a Compare method. In addition, you can make the sort method safer by writing it so that it only works with arrays of classes that implement an ICompare interface using introspection. There are lots of useful examples to be found in the framework in facilities introduced before generics, see the next chapter. This is an extreme example of using casting to implement generic algorithms. In many cases the function will accept a base class and work with it and all its derived classes courtesy of upcasting. For example: public base[] MyFunction(base[] array) { //do the sort return array; } enables you to pass any object of the base class or a derived class. MyFunction can only make use of methods that the base class has unless it uses a downcast to the appropriate derived class and then the problem is making sure that that it is working with the correct derived class. The result of the function is returned as an array of base types and usually this would have to be downcast to the type of the object passed into the function. Of course, any mistakes in downcasting result in run-time errors because they cannot be picked up at compile time. Put simply, this approach to writing generic algorithms is not type safe. If you abandon strong typing by using casts then this is the sort of problem you have to live with. The alternative approach is to use generics as outlined in the next chapter. While generics are type safe the disadvantage is that they achieve this by being very restrictive and ultimately very complicated. |