|Casting – the escape from strong typing|
Page 2 of 5
In many ways casting is a construct that is intended to cope with some problems inherent in the mixing of strong typing with object-oriented programming.
That is, casting is mostly about changing the type label on a variable, not active conversion of representations. You can view its involvement with data format conversion of value types as an accident due to the need for some consistency. Indeed C# modelled its casting on C++ just at the point where C++ was attempting to put its house in order by adding some constructs that removed the need to use casts.
When you define a new class it always has a relationship to the classes it inherits from. At its simplest this could be just Object, which is the root of the inheritance hierarchy. In many cases a class will also have a relationship with classes that have been derived from it. As a class that inherits from another class generally adds properties and methods you can think of the new class as being in some sense “bigger” than the base class.
That is, if we define two classes:
then ClassB has all of the methods and properties of ClassA plus some of its own. Thus ClassB is “bigger” than ClassA.
What this means is that you can treat a ClassB object as if it was in fact a ClassA object. In this case you would be ignoring the new bits added by defining ClassB and simply restricting your access to ClassA methods and properties. This “downsizing” of ClassB is generally called an upcast because it moves you up the object hierarchy towards the root object.
Upcasting, moving up the inheritance hierarchy, is always safe
For example ClassB inherits methodA from ClassA:
Now we can create a ClassA variable and make it reference a ClassB object:
This works without the need for an explicit cast because it is safe in the sense that you can call any ClassA members and expect them to be present in ClassB.
That is, you can use:
Casting and overriding
There is one other slightly subtle point – if ClassB redefines MethodA which class’s method is used in an upcast?
The answer depends on exactly how the redefinition is performed either using new or override. This difference goes straight to the heart of why there are two ways of redefining methods. If you redefine a method using the new modifier then the upcast:
will always call the MethodA defined in ClassA.
Of course this return to the original is one of the main reasons for upcasting.
However, this doesn’t always work because if the method is redefined using the override modifier then the upcast will still call the version defined in ClassB.
You can think of the difference as being related to where the modification to the method occurs in the class hierarchy. Using the keyword new results in the method being redefined down the hierarchy whereas the override modifier redefines the method both up and down the hierarchy.
You might think that upcasting is a bit esoteric for you to want to make use of it but it is implicitly used in other C# constructs that rely on “polymorphism”. For example, if you create a collection of classes derived from a base class then iterating through the collection will return a reference to objects of different types according to what is actually stored in the collection. In this case each object will be implicitly upcast to the base type of the collection. This means that for a method that is redefined in the derived classes which method you actually call will depend on which of new or override was used when redefining the method.
Notice that you don’t have to use upcasting to call base class methods within the definition of a class because you can use the base keyword instead.