74. Upcasting#

In the chapter on subtype polymorphism we learned about substitutability and that an object of a subtype can be used where an object of its supertype is expected. This means that an apple can be treated as a fruit, if we choose to forget about the characteristics that are specific to apples. When we use an object of a subtype where its supertype is expected, we perform an implicit type conversion called “upcasting”.

Key point

Upcasting is the process of type converting a derived class or interface implementation reference into a base class or interface reference. This conversion is always safe and is implicitly done by the compiler.

Note

Upcasting is implicit in many object oriented languages, including C#.

Let’s look at an example. Apples and bananas are both fruit. We might model this as an interface called IFruit and two classes, called Apple and Banana, that both implement this interface.

interface IFruit
{
    void Eat();
}
class Apple : IFruit
{
    public void Eat()
        => Console.WriteLine("Eating an apple.");
}
class Banana : IFruit
{
    public void Eat()
        => Console.WriteLine("Eating a banana.");
}

Both Apple and Banana implement the IFruit interface, thus, they are also IFruit’s. If we want to treat an Apple or Banana simply as an IFruit, we perform upcasting.

Apple apple = new Apple();
IFruit fruit1 = apple; // Upcasting

IFruit fruit2 = new Banana(); // Upcasting

In the above code, the Apple and Banana objects are both upcast to IFruit. The compiler knows that an Apple and Banana are IFruit’s, so it compiles these assignment statements without any explicit casting. Since upcasting always is safe, we can also be sure that this code won’t throw an exception at run-time.

While upcasting is performed implicitly by the compiler, we can still make it explicit if we choose to. Although it’s unnecessary, and doesn’t provide any additional safety or performance benefits, it could be used for clarity in code, to make it clear that we’re aware that we are using a subtype in place of its supertype. Here is an example:

Apple apple = new Apple();
IFruit fruit = (IFruit) apple; // Explicit upcasting

The second statement is equivalent to IFruit fruit = apple; as both of these statements result in fruit being a reference to an Apple object, seen through the ‘lens’ of the IFruit interface.

Take some time to experiment with upcasting and try it out with your own classes. In the next chapter, we’ll explore downcasting, which, in contrast to upcasting, is not implicit and not safe. Downcasting can be thought of as trying to reveal the hidden specifics.