Menu
Coddy logo textTech

Upcasting & Downcasting

Part of the Object Oriented Programming section of Coddy's C# journey — lesson 29 of 70.

When working with inheritance hierarchies, you often need to convert between base and derived types. Upcasting converts a derived type to its base type, while downcasting does the opposite.

Upcasting is implicit and always safe because a derived class is guaranteed to have everything the base class has:

Dog myDog = new Dog();
Animal animal = myDog;  // Upcasting - implicit, always works

Downcasting requires an explicit cast and can fail at runtime if the object isn't actually the type you're casting to:

Animal animal = new Dog();
Dog dog = (Dog)animal;  // Downcasting - works because it's really a Dog

Animal cat = new Cat();
Dog wrong = (Dog)cat;   // Throws InvalidCastException!

To downcast safely, use the is keyword to check the type first, or use as which returns null instead of throwing an exception:

if (animal is Dog d)
{
    d.Bark();  // Safe - d is only available if check passes
}

Dog maybeDog = animal as Dog;
if (maybeDog != null)
{
    maybeDog.Bark();
}

The is pattern matching approach is preferred in modern C# because it combines the type check and cast in one step, making your code both safer and cleaner.

challenge icon

Challenge

Easy

Let's build a vehicle inspection system that demonstrates how upcasting and downcasting let you work flexibly with objects in an inheritance hierarchy. You'll create different vehicle types and write code that safely identifies and interacts with specific vehicle kinds.

You'll organize your code across four files:

  • Vehicle.cs: Define a base Vehicle class in the Inspection namespace. Every vehicle has a LicensePlate property (string) set through a constructor. Include a method called GetBasicInfo() that returns "Vehicle: {LicensePlate}".
  • Car.cs: Define a Car class in the Inspection namespace that inherits from Vehicle. Cars have an additional NumberOfDoors property (int). The constructor accepts both license plate and door count. Add a method called Honk() that returns "{LicensePlate} honks: Beep beep!".
  • Motorcycle.cs: Define a Motorcycle class in the Inspection namespace that inherits from Vehicle. Motorcycles have a HasSidecar property (bool). The constructor accepts license plate and sidecar status. Add a method called Rev() that returns "{LicensePlate} revs: Vroom!".
  • Program.cs: In your main file, you'll create vehicles and practice both upcasting and safe downcasting. Create a Car and a Motorcycle using input values, then store both in an array of Vehicle references (upcasting). Loop through the array and for each vehicle, print its basic info. Then use the is keyword with pattern matching to safely downcast: if it's a Car, call Honk(); if it's a Motorcycle, call Rev().

You will receive four inputs:

  • Car license plate
  • Number of doors
  • Motorcycle license plate
  • Has sidecar (true or false)

Print the output in this format for each vehicle in the array:

{GetBasicInfo() result}
{Honk() or Rev() result based on actual type}

For example, if the inputs are ABC-123, 4, XYZ-789, and false, the output should be:

Vehicle: ABC-123
ABC-123 honks: Beep beep!
Vehicle: XYZ-789
XYZ-789 revs: Vroom!

Notice how all vehicles are stored as Vehicle references (upcasting), but you can safely discover their true types and access type-specific methods using the is pattern matching syntax for downcasting!

Cheat sheet

When working with inheritance hierarchies, upcasting converts a derived type to its base type, while downcasting converts a base type to a derived type.

Upcasting is implicit and always safe:

Dog myDog = new Dog();
Animal animal = myDog;  // Upcasting - implicit, always works

Downcasting requires an explicit cast and can fail at runtime:

Animal animal = new Dog();
Dog dog = (Dog)animal;  // Downcasting - works because it's really a Dog

Animal cat = new Cat();
Dog wrong = (Dog)cat;   // Throws InvalidCastException!

Safe downcasting using the is keyword with pattern matching (preferred):

if (animal is Dog d)
{
    d.Bark();  // Safe - d is only available if check passes
}

Safe downcasting using the as keyword (returns null if cast fails):

Dog maybeDog = animal as Dog;
if (maybeDog != null)
{
    maybeDog.Bark();
}

Try it yourself

using System;
using Inspection;

class Program
{
    public static void Main(string[] args)
    {
        // Read inputs
        string carPlate = Console.ReadLine();
        int numberOfDoors = Convert.ToInt32(Console.ReadLine());
        string motorcyclePlate = Console.ReadLine();
        bool hasSidecar = Convert.ToBoolean(Console.ReadLine());
        
        // TODO: Create a Car object using carPlate and numberOfDoors
        
        // TODO: Create a Motorcycle object using motorcyclePlate and hasSidecar
        
        // TODO: Create an array of Vehicle references containing both vehicles (upcasting)
        
        // TODO: Loop through the array and for each vehicle:
        //   - Print the result of GetBasicInfo()
        //   - Use 'is' keyword with pattern matching to safely downcast:
        //     - If it's a Car, call and print Honk()
        //     - If it's a Motorcycle, call and print Rev()
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming