Polymorphism in depth with C#


polymorphism in csharp

Polymorphism is one of the core concept of object oriented programming. Word polymorphism = poly (many) + morphism (forms). As its name suggesting polymorphism means, an entity can have multiple forms.

šŸ“¢ Updated and refinded at : 21-feb-2025

Other oops core concepts :

Letā€™s learn polymorphism through mistakes.

Jim requires an entry system for his pet shop, which exclusively houses various breeds of dogs. This system will manage the entry and records of all dogs entering and exiting the premises.

pet shop

Photo by Nancy Guth from Pexels

It is easy, rightā€¦ So lets create the system.

public class Dog
{
    public string Name { get; }
    public string Breed { get; }
    public string Color { get; }

    public Dog(string name, string breed, string color)
    {
    Name = name;
    Breed= breed;
    Color = color;
    }

    public void Eat()
    {
    Console.WriteLine("Eating");
    }

    public void Sleep()
    {
    Console.WriteLine("Sleeping");
    }

    public void MakeSound()
    {
    Console.WriteLine("Woff!");
    }
}

public class PetShopEntrySystem
{
    private List<Dog> dogs;

    public PetShopEntrySystem()
    {
        dogs = new List<Dog>();
    }

    public void AddDog(Dog dog)
    {
        dogs.Add(dog);
    }

    public IEnumerable<Dog> GetDogs()
    {
        return dogs;
    }
}

public class PetshopEntrySimulator
{
    static void Main()
    {
        Dog dog1 = new Dog("Max", "Golden Retriever", "Golden");
        Dog dog2 = new Dog("Luna", "Sibarian Husky", "Grey/White");
        Dog dog3 = new Dog("Duke", "Labrador Retriever", "Chocolate Brown");

        PetShopEntrySystem entrySystem = new PetShopEntrySystem();
        entrySystem.AddDog(dog1);
        entrySystem.AddDog(dog2);
        entrySystem.AddDog(dog3);

        var dogs = entrySystem.GetDogs();
        DisplayDogs(dogs);
    }

    private static void DisplayDogs(IEnumerable<Dog> dogs)
    {
        foreach (var dog in dogs)
        {
            Console.WriteLine($"Name: {dog.Name}, Breed: {dog.Breed}, Color: {dog.Color}");
        }
    }

}

Jim is pleased with his current system, which efficiently tracks all records of the dogs in his pet shop. As his business is expanding, Jim now wants to include cats in his inventory.

dog and cat

Photo by Nadia Vasilā€™eva from pexels

To accommodate this, we need to create a Cat class and integrate it into the existing entry system, similar to the process used for dogs.

public class Cat
{
    public string Name { get; }
    public string Breed { get; }
    public string Color { get; }

    public Cat(string name, string breed, string color)
    {
        Name = name;
        Breed = breed;
        Color = color;
    }

    public void Eat()
    {
        Console.WriteLine("Eating");
    }

    public void Sleep()
    {
        Console.WriteLine("Sleeping");
    }

    public void MakeSound()
    {
        Console.WriteLine("Meau...!");
    }

}

// refactored pet shop entry system

public class PetShopEntrySystem
{
    private List<Dog> dogs;
    private List<Cat> cats;

    public PetShopEntrySystem()
    {
        dogs = new List<Dog>();
        cats = new List<Cat>();
    }

    public void AddDog(Dog dog)
    {
        dogs.Add(dog);
    }

    public void AddCat(Cat cat)
    {
        cats.Add(cat);
    }

    public IEnumerable<Dog> GetDogs()
    {
        return dogs;
    }

    public IEnumerable<Cat> GetCats()
    {
        return cats;
    }

}

// Pet shop entry simulator for testing the application

public class PetshopEntrySimulator
{
   static void Main()
   {
        // existing code for dog entries

        // enterting cat's data
        Cat cat1 = new Cat("Whiskers", "Persian", "White");
        Cat cat2 = new Cat("Mittens", "Maine Coon", "Grey/Tabby");
        Cat cat3 = new Cat("Ginger", "British Shorthair", "Orange/Ginger");

        PetShopEntrySystem entrySystem = new PetShopEntrySystem();
        entrySystem.AddCat(cat1);
        entrySystem.AddCat(cat2);
        entrySystem.AddCat(cat3);

        var cats = entrySystem.GetCats();
        DisplayCats(cats);
    }

    // existing DisplayDogsMethod

    private static void DisplayCats(IEnumerable<Cat> cats)
    {
        foreach (var cat in cats)
        {
            Console.WriteLine($"Name: {cat.Name}, Breed: {cat.Breed}, Color: {cat.Color}");
        }
    }

}

Certainly! To streamline the entry system for Jimā€™s pet shop and make it scalable for additional pet types, we can create a common Animal class to encapsulate all shared attributes and methods. Hereā€™s how we can refactor the system:

public class Animal
{
    public string Name { get; }
    public string Breed { get; }
    public string Color { get; }

    public Animal(string name, string breed, string color)
    {
        Name = name;
        Breed = breed;
        Color = color;
    }
    public virtual void Eat()
    {
        Console.WriteLine("Eating");
    }

    public void Sleep()
    {
        Console.WriteLine("Sleeping");
    }

    public virtual void MakeSound()
    {
        Console.WriteLine("making sound");
    }

}

Since cats and dogs may have different eating habits and make different sounds, we make these methods virtual in the Animal class so that they can be overridden in their respective classes. The Sleep method will remain the same for all classes.

Letā€™s refactor the Dog , Cat and PetShopEntrySystem classes.

public class Cat : Animal
{
  public Cat(string name, string breed, string color)
  : base(name, breed, color)
  {

  }

  public override void Eat()
  {
     Console.WriteLine("Cat is eating");
  }

  public override void MakeSound()
  {
     Console.WriteLine("Meau...!");
  }
}


public class Dog : Animal
{
  public Dog(string name, string breed, string color)
  : base(name, breed, color)
  {
  }

  public override void Eat()
  {
     Console.WriteLine("Dog is Eating");
  }

  public override void MakeSound()
  {
     Console.WriteLine("Woff! woof");
  }
}

public class PetShopEntrySystem
{
   private readonly List<Animal> animals;

   public PetShopEntrySystem()
   {
      animals = new List<Animal>();
   }

   public void AddAnimal(Animal animal)
   {
      animals.Add(animal);
   }

   public IEnumerable<Animal> GetAnimals()
   {
       return animals;
   }

}

We now maintain a single list of animals, and provide a single method for adding an animal and retrieving all animals. Letā€™s test the system.

public class PetshopEntrySimulator
{
  static void Main()
  {
        Animal cat1 = new Cat("Whiskers", "Persian", "White");
        Animal cat2 = new Cat("Mittens", "Maine Coon", "Grey/Tabby");
        Animal cat3 = new Cat("Ginger", "British Shorthair", "Orange/Ginger");
        cat1.Eat();
        cat1.Sleep();
        cat1.MakeSound();

        Dog dog1 = new Dog("Max", "Golden Retriever", "Golden");
        Dog dog2 = new Dog("Luna", "Sibarian Husky", "Grey/White");
        Dog dog3 = new Dog("Duke", "Labrador Retriever", "Chocolate Brown");

        PetShopEntrySystem entrySystem = new PetShopEntrySystem();

        // adding cats
        entrySystem.AddAnimal(cat1);
        entrySystem.AddAnimal(cat2);
        entrySystem.AddAnimal(cat3);

        // adding dogs
        entrySystem.AddAnimal(dog1);
        entrySystem.AddAnimal(dog2);
        entrySystem.AddAnimal(dog3);

        var animals = entrySystem.GetAnimals();
        DisplayAnimals(animals);
    }

    private static void DisplayAnimals(IEnumerable<Animal> animals)
    {
        foreach (var animal in animals)
        {
            Console.WriteLine($"Name: {animal.Name}, Breed: {animal.Breed}, Color: {animal.Color}");
        }
    }

}

We now have a single method for adding and displaying animals. You can add any number of animal types without changing the code in the Animal and PetShop classes. Our system is very flexible now.

However, instantiating the Animal class directly, like Animal animal = new Animal();, doesnā€™t make sense. What kind of animal is it? What is its behavior? These questions canā€™t be answered just by looking at this instantiation. Therefore, it makes sense to convert the Animal class into an abstract class.

public abstract class Animal
{
   //remaining code
}

We override the MakeSound() and Eat() methods in every concrete class. There is no benefit in having concrete implementations of MakeSound() and Eat() methods in the Animal class. Therefore, letā€™s make these methods abstract.

public abstract class Animal
{
    public string Name { get; }
    public string Breed { get; }
    public string Color { get; }

    public Animal(string name, string breed, string color)
    {
        Name = name;
        Breed = breed;
        Color = color;
    }

    public void Sleep()
    {
        Console.WriteLine("Sleeping");
    }

    public abstract void Eat();
    public abstract void MakeSound();

}

We can relate this to the definition ā€œan object can take many forms.ā€

A Dog is an Animal, a Cat is an Animal, and a Cow is an Animal too. An Animal can take many forms (Dog, Cat, Cow, and many more). We can easily replace one animal type with another at runtime, providing flexibility.

Dog dog = new Dog(); // This is not polymorphism

Animal dog = new Dog(); // This is polymorphism

Letā€™s understand it in simpler terms.

some image


Polymorphism through interfaces

āš ļøNote: Polymorphism can also be achieved through interfaces. Simply replace the base class with an interface.

---
title: Polymorphism through interfaces
---
classDiagram
    class INotification {
        <<interface>>
        Send()
    }

    class EmailNotification {
        +Send()
    }

    class SmsNotification {
        +Send()
    }

    INotification <|-- EmailNotification
    INotification <|-- SmsNotification
  

C# implementation:

public interface INotification
{
    void Send();
}

public class EmailNotification : INotification
{
    public void Send()
    {
       // Email sending logic
    }
}

public class SmsNotification : INotification
{
    public void Send()
    {
       // SMS sending logic
    }
}

Here, INotification is a base type, and SmsNotification and EmailNotification are the derived types.

Why did we use inheritance before?

Because we wanted to reuse the functionality like Name, Breed, and Sleep() of the Animal class. Be careful when choosing between an interface and an abstract class as a base type. This concept is also known as subtype polymorphism and is an example of runtime polymorphism.

Why is it called runtime polymorphism?

When you call a method (like MakeSound) on an Animal reference, the computer needs to figure out which specific method to use. Is it the dogā€™s bark or the catā€™s meow? This decision is made while the program is running, not when the code is being written or compiled. Thatā€™s why itā€™s called ā€œruntimeā€ polymorphism.

Method Overloading (Compile-time Polymorphism)

It is debatable whether to call method overloading polymorphism. Some consider it compile-time polymorphism, while others do not. I thought it should be included in this discussion.

Method overloading occurs when two or more methods in the same class have the same name but different parameters. The compiler determines which method to invoke based on the method signature. It is called compile time polymorphism because at the compile time, compiler knows which method it is calling.

class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Add(int a, int b,int c)
    {
        return a + b + c;
    }

    public double Add(double a, double b)
    {
        return a + b;
    }

}

In this example, the Add method is overloaded to handle both integer and double parameters. Calculator class have two Add methods but with different number of arguments.

Summary

  • An entity can have multiple forms, eg. Animal can have many forms like Cat and Dog.
---
title: Polymorphism
---

classDiagram
  class Animal{
    <<abstract class>>
    MakeSound()*
  }

  Animal <|-- Dog
  Animal <|-- Cat
  
  • Polymorphism is achieved through either an abstract class or an interface.
  • It is also called run time polymorphism or late binding, because till the run time it does not which MakeSound() method it is using. Is it a dogā€™s bark or catā€™s meaw.
  • Additionally, there is also a term called method overloading. It allows multiple methods with the same name but different parameters in a single class. Some consider it as a early binding or compile time polymorphism and some donā€™t consider it as a polymorphism at all.

Original post by Ravindra Devrani on February 5, 2024. Updated and refinded at : 21-feb-2025. Canonical link.