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 worksDowncasting 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
EasyLet'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 baseVehicleclass in theInspectionnamespace. Every vehicle has aLicensePlateproperty (string) set through a constructor. Include a method calledGetBasicInfo()that returns"Vehicle: {LicensePlate}".Car.cs: Define aCarclass in theInspectionnamespace that inherits fromVehicle. Cars have an additionalNumberOfDoorsproperty (int). The constructor accepts both license plate and door count. Add a method calledHonk()that returns"{LicensePlate} honks: Beep beep!".Motorcycle.cs: Define aMotorcycleclass in theInspectionnamespace that inherits fromVehicle. Motorcycles have aHasSidecarproperty (bool). The constructor accepts license plate and sidecar status. Add a method calledRev()that returns"{LicensePlate} revs: Vroom!".Program.cs: In your main file, you'll create vehicles and practice both upcasting and safe downcasting. Create aCarand aMotorcycleusing input values, then store both in an array ofVehiclereferences (upcasting). Loop through the array and for each vehicle, print its basic info. Then use theiskeyword with pattern matching to safely downcast: if it's aCar, callHonk(); if it's aMotorcycle, callRev().
You will receive four inputs:
- Car license plate
- Number of doors
- Motorcycle license plate
- Has sidecar (
trueorfalse)
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 worksDowncasting 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()
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesNamespaces & DirectivesIntro to Classes & ObjectsThe 'this' KeywordMethods and ParametersFields vs PropertiesConstructorsObject InitializersRecap - Simple Calculator4Inheritance
Basic Inheritance (:) SyntaxThe 'base' KeywordVirtual & Override KeywordsSealed ClassesThe 'object' Base ClassRecap - Employee Hierarchy7Advanced Features
Operator OverloadingIndexers (this[])ToString() OverrideExtension MethodsRecap - Custom List2Properties & Static Members
Auto-Implemented PropertiesRead/Write-Only PropertiesStatic Fields & MethodsStatic ClassesExpression-Bodied Members5Polymorphism & Interfaces
Compile vs Runtime PolyInterface vs Abstract ClassMultiple InterfacesExplicit InterfacesUpcasting & DowncastingRecap - Shape Calculator