Understanding Polymorphism in OOP made Easy

In this detailed article, let's understand what is Polymorphism, types, features and how it works with illustrating examples in C#

Introduction

Polymorphism is one of the key characteristics of an Object Oriented Programming Language. The other characteristics are Abstraction, Encapsulation and Inheritance. Any Object Oriented Programming Language must have features that support these characteristics.

What is Polymorphism?

Polymorphism is one of the four characteristic properties which are core to any object oriented programming language.

The polymorphism is a scenario among base and derived types.

A base type can be used to invoke a method of its derived type, which is in contrast to the behavior of Inheritance where a derived type accesses its base type methods.

Polymorphism can be summarized as below –

  1. opposite of inheritance
  2. one object manipulated into many forms

What are the different types of Polymorphism?

When it comes to deciding which method needs to be invoked for a calling reference type, there are two kinds of behaviors which can be observed.

There are two kinds of polymorphic behaviors which can be noticed in object oriented programming structures. They are –

  1. Compile-time Polymorphism
  2. Runtime Polymorphism

Understanding Polymorphism with an example in C#

To understand these behaviors, let’s take the example of a base Animal type which exposes a virtual method Sound().

And there are two derived types Cat and Dog classes which override this behavior with their own implementations of the Sound() method.

The structure looks as below –

public class Animal
{
    public virtual void Sound()
    {
        Console.WriteLine("Animal makes Sound - ...");
    }
}

public class Cat : Animal
{
    public override void Sound()
    {
        Console.WriteLine("Cat makes Sound - Meow");
    }
}

public class Dog : Animal
{
    public override void Sound()
    {
        Console.WriteLine("Dog makes Sound - Bow");
    }
}

And let’s assume we invoke these methods in the following fashion in our Main() method –

namespace WorkConsoleApp
{
    public class NewProgram
    {
        public static void Main(string[] args)
        {
            // reference of Base Animal
            Animal animal;


            // Derived type Cat
            // assigned to
            // Base type Animal
            animal = new Cat();
            animal.Sound();


            // Derived type Dog
            // assigned to
            // Base type Animal
            animal = new Dog();
            animal.Sound();
        }
    }
}

// Output
Cat makes Sound - Meow
Dog makes Sound - Bow

Here we declare a variable animal which is of type Animal, and we assign it sequentially to each of its derived types namely Cat and Dog.

When we call the method animal.Sound() over the type Animal, the variable still being of type Animal calls its derived class methods.

The compiler doesn’t know what is stored for the variable animal.

What is Runtime Polymorphism?

During runtime, the animal variable is dynamically assigned the values new Cat() and new Dog() which represent the object instances of the types Cat() and Dog() respectively.

Later when the line of code animal.Sound() is executed, the runtime makes a decision for what needs to be called for animal.Sound().

One could have expected the base class method Animal.Sound() be called, but the runtime decides to call its derived type Cat.Sound() and Dog.Sound() to be called instead.

Since the decision is delayed till runtime and is made only during runtime, this is called Runtime Polymorphism.

Features of Runtime Polymorphism

  1. Decision of invocation happens at runtime
  2. Occurs for an Abstract or Concrete base with a virtual method and its overriding derived types
  3. Occurs under the context of inheritance and method overriding
  4. Is also known as Lazy binding or Late Binding
  5. Is somewhat slow and might be prone to runtime errors

Alternatively, consider another class Beast which has two methods of the same name Sound() and is defined as below.

public class Beast
{
    public void Sound()
    {
        Console.WriteLine("Beast makes a sound - grrr");
    }


    public void Sound(string sound)
    {
        Console.WriteLine("Beast makes a sound of passed string {sound}");
    }


    public void Sound(object sound)
    {
        Console.WriteLine("Beast makes a sound of passed object {sound}");
    }
}

Inside the Main() method, we create an object of type Beast and call the Sound() method with various parameters as below.

public class NewProgram
{
    public static void Main(string[] args)
    {
        Beast b = new Beast();


        // call the default
        // Sound() method
        b.Sound();


        // call Sound()
        // with a string param
        b.Sound("PowPow");


        // call Sound()
        // with an int param
        b.Sound(12345);
    }
}


// Output
Beast makes a sound - grrr
Beast makes a sound of passed string PowPow
Beast makes a sound of passed object 12345

Notice that we have called all the three variations of the method Sound() with different parameter types. We have passed a string parameter “PowPow” and an integer 12345 to the method.

What is Compile-time polymorphism?

The decision to call which variation of the method Sound() is taken up by the compiler while linking, and maps the references to the respective methods depending on the parameter type.

Since all this decision making happens even before the code is executed, we call this behavior as Compile-time polymorphism.

Features of Compile-time Polymorphism

  1. Decision of invocation is finalized during compilation
  2. Occurs for a variants of a method within a class definition and when the method is invoked with varying parameters
  3. Occurs under the context of method or property overloading and when the derived type methods are invoked with their own instances
  4. Is also known as Early binding or Static Binding
  5. Is faster than what Late binding does but is not flexible

Observe that for the method invocation b.Sound(“PowPow”) calls the method overload with a string type parameter. which is in this case the best match for the passed input type.

For the second call b.Sound(12345) the method overload with object type parameter is invoked, since an integer is-an object and the best available match for the integer here is that of its base type object.


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *