DEV Community

Cover image for What is Polymorphism in C# - Explained with a Code Example
Danny Adams
Danny Adams

Posted on

What is Polymorphism in C# - Explained with a Code Example

The word polymorphism is derived from Greek, and means "having multiple forms":

  • Poly = many

  • Morph = forms

In programming, polymorphism is the ability of an object to take many forms.

To help you to understand polymorphism, we will first go through an example that doesn't use polymorphism. We will then discuss the issues with this solution, then refactor it to use polymorphism. This will give you a good understanding of what polymorphism is, and its ability to make software more flexible, extendable, testable, readable and elegant.

A bad example with no polymorphism:

public class Car
{
 public string Brand { get; set; }
 public string Model { get; set; }
 public int Year { get; set; }

 public int NumberOfDoors { get; set; }

 public void Start()
 {
   Console.WriteLine("Car is starting.");
 }

 public void Stop()
 {
   Console.WriteLine("Car is stopping.");
 }
}
Enter fullscreen mode Exit fullscreen mode
public class Motorcycle
{
 public string Brand { get; set; }
 public string Model { get; set; }
 public int Year { get; set; }

 public void Start()
 {
   Console.WriteLine("Motorcycle is starting.");
 }

 public void Stop()
 {
   Console.WriteLine("Motorcycle is stopping.");
 }
}
Enter fullscreen mode Exit fullscreen mode

Let’s say that we want to create a list of vehicles, then loop through it and perform an inspection on each vehicle:

// Create a list of objects
List<Object> vehicles = new List<Object>
    {
      new Car { Brand = "Toyota", Model = "Camry", Year = 2020, NumberOfDoors = 4 },
      new Motorcycle { Brand = "Harley-Davidson", Model = "Sportster", Year = 2021 }
    };


// Perform a general inspection on each vehicle
foreach (var vehicle in vehicles)
{
 if (vehicle is Car)
 {
   Car car = (Car)vehicle; // cast vehicle to a Car
   Console.WriteLine($"Inspecting {car.Brand} {car.Model} ({car.GetType().Name})");
   car.Start();
   car.Stop();
 }
 else if (vehicle is Motorcycle)
 {
   Motorcycle motorcycle = (Motorcycle)vehicle; // cast vehicle to a Motorcycle
   Console.WriteLine($"Inspecting {motorcycle.Brand} {motorcycle.Model} ({motorcycle.GetType().Name})");
   motorcycle.Start();
   motorcycle.Stop();
 }
 else
 {
   throw new Exception("Object is not a valid vehicle");
 }
}
Enter fullscreen mode Exit fullscreen mode

Notice the ugly code inside the foreach loop! Because vehicles is a list of any type of object (Object), we have to figure out what type of object we are dealing with in each loop, then cast it to the appropriate object type before we can access any information on the object.

This code will continue to get uglier as we add more vehicle types. For example, if we extended our codebase to include a new Plane class, then we’d need to modify existing code – we’d have to add another conditional check in the foreach loop for planes, violating the Open/Closed SOLID Principle.

(By the way, if you're unfamiliar with the SOLID Principles and want to take your OOP skills to the next level, learning everything that you need to write elegant, maintainable, flexible and testable software -- then check out my book on Amazon Mastering Design Patterns in C#: A Beginner-Friendly Guide, Including OOP and SOLID Principles. It's also available on Gumroad.)

Now, back to our example...

Introducing: Polymorphism

Cars and motorcycles are both vehicles. They both share some common properties and methods. So, let’s create a parent class that contains these shared properties and methods:

public class Vehicle
{
 public string Brand { get; set; }
 public string Model { get; set; }
 public int Year { get; set; }

 public virtual void Start()
 {
   Console.WriteLine("Vehicle is starting.");
 }

 public virtual void Stop()
 {
   Console.WriteLine("Vehicle is stopping.");
 }
}
Enter fullscreen mode Exit fullscreen mode

Car and Motorcycle can now inherit from Vehicle:

public class Car : Vehicle
{
 public int NumberOfDoors { get; set; }

 public override void Start()
 {
   Console.WriteLine("Car is starting.");
 }

 public override void Stop()
 {
   Console.WriteLine("Car is stopping.");
 }
}
Enter fullscreen mode Exit fullscreen mode
public class Motorcycle : Vehicle
{
 public override void Start()
 {
   Console.WriteLine("Motorcycle is starting.");
 }

 public override void Stop()
 {
   Console.WriteLine("Motorcycle is stopping.");
 }
}
Enter fullscreen mode Exit fullscreen mode

Car and Motorcycle both extend Vehicle, as they are vehicles. But what’s the point in Car and Motorcycle both extending Vehicle if they are going to implement their own versions of the Start() and Stop() methods? Look at the code below:

// Program.cs

// Create a list of vehicles
List<Vehicle> vehicles = new List<Vehicle>
     {
       new Car { Brand = "Toyota", Model = "Camry", Year = 2020, NumberOfDoors = 4 },
       new Motorcycle { Brand = "Harley-Davidson", Model = "Sportster", Year = 2021 }
     };

// Perform a general inspection on each vehicle
foreach (var vehicle in vehicles)
{
 Console.WriteLine($"Inspecting {vehicle.Brand} {vehicle.Model} ({vehicle.GetType().Name})");
 vehicle.Start();
 // Additional inspection steps...
 vehicle.Stop();
 Console.WriteLine();
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  • We have a list, vehicles, containing instances of both Car and Motorcycle.
  • We iterate through each vehicle in the list and perform a general inspection on each one.
  • The inspection process involves starting the vehicle, checking its brand and model, and stopping it afterwards.
  • Despite the vehicles being of different types, polymorphism allows us to treat them all as instances of the base Vehicle class. The specific implementations of the Start() and Stop() methods for each vehicle type are invoked dynamically at runtime, based on the actual type of each vehicle.

Because the list can only contain objects that extend the Vehicle class, we know that every object will share some common fields and methods. This means that we can safely call them, without having to worry about whether each specific vehicle has these fields or methods.

This demonstrates how polymorphism enables code to be written in a more generic and flexible manner, allowing for easy extension and maintenance as new types of vehicles are added to the system.

For example, if we wanted to add another vehicle, we don’t have to modify the code used to inspect vehicles (“the client code”); we can just extend our code base, without modifying existing code:

public class Plane : Vehicle
{
 public int NumberOfDoors { get; set; }

 public override void Start()
 {
   Console.WriteLine("Plane is starting.");
 }

 public override void Stop()
 {
   Console.WriteLine("Plane is stopping.");
 }
}
Enter fullscreen mode Exit fullscreen mode
// Program.cs

// Create a list of vehicles
List<Vehicle> vehicles = new List<Vehicle>
      {
          new Car { Brand = "Toyota", Model = "Camry", Year = 2020, NumberOfDoors = 4 },
          new Motorcycle { Brand = "Harley-Davidson", Model = "Sportster", Year = 2021 },
          /////////// ADD A PLANE TO THE LIST:
          new Plane { Brand = "Boeing", Model = "747", Year = 2015 }
          ////////////////////////////////////
      };
Enter fullscreen mode Exit fullscreen mode

The code to perform the vehicle inspections doesn’t have to change to account for a plane. Everything still works, without having to modify our inspection logic.

Conclusion

Polymorphism is the term that explains different types of objects can be treated the same way, allowing us to flexibly pass around different types of objects in our code. Polymorphism makes software flexible, extensible, testable (e.g. we can pass a dummy database class to an object so that we don't have to test with a real database) and readable.

And if you'd like to learn all of the tools that you need to become a great object-oriented programmer, including:

  • All 23 design patterns (“The Gang of Four Design Patterns”) with real world examples.
  • OOP principles: encapsulation, abstraction, inheritance, polymorphism, coupling, composition, composition vs inheritance, fragile base class problem.
  • The five SOLID principles: crucial for any developer that wants to work on OOP software and essential to know before diving into the design patterns.
  • Unified Modelling Language (UML): the standard way to model classes and the relationships between them.

Then check out my book on Amazon Mastering Design Patterns in C#: A Beginner-Friendly Guide, Including OOP and SOLID Principles. It's also available on Gumroad.

Hope this article was helpful!

Thanks,
Danny

Top comments (0)