Polymorphism Types In OOPs

Adarsh Kumar
6 min readFeb 14, 2024

--

In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types, or the use of a single symbol to represent multiple different types. A polymorphic object is an object that is capable of taking on multiple forms, just like your ex– just kidding!

Compile-time Polymorphism (Static Binding or Early Binding or Ad hoc polymorphism):

There are two types of compile time polymorphism exist one is operator overloading and other is method overloading

Method Overloading: Allows a class to have multiple methods with the same name but different parameters. Here only difference in parameter only not return type of method.

using System;

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

// Overloaded method with different parameter types
public double Add(double a, double b)
{
return a + b;
}
// this is not possible can't have same paramter but different return type
public int Add(double a, double b)
{
return (int) a + b;
}
}

class Program
{
static void Main(string[] args)
{
Calculator calc = new Calculator();
int result1 = calc.Add(10, 20);
double result2 = calc.Add(3.5, 4.5);

Console.WriteLine("Result 1: " + result1);
Console.WriteLine("Result 2: " + result2);
}
}

Operator Overloading: Allows operators to be redefined to work with user-defined types. Like you can add number in string type.

using System;

public class Complex
{
public int Real { get; set; }
public int Imaginary { get; set; }

public Complex(int real = 0, int imaginary = 0)
{
Real = real;
Imaginary = imaginary;
}

// Overloading the '+' operator for adding two Complex objects
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
}
// Overloading the '!=' operator for adding two Complex objects
public static bool operator !=(Complex first, Complex second)
{
return !(first.Real == second.Real && first.Imaginary == second.Imaginary);
}

public void Display()
{
Console.WriteLine($"Real: {Real}, Imaginary: {Imaginary}");
}
}

class Program
{
static void Main(string[] args)
{
Complex c1 = new Complex(3, 4);
Complex c2 = new Complex(5, 6);
Complex result = c1 + c2; // Using the overloaded '+' operator
result.Display();
}
}

in the above example complex class overloaded + and != operator, using operator overloading you are providing custom natural operator overloading either -, +, * or = etc. All things you define in compile time that’s why this called compile time overloading. By default in c# there is default + operator overloading between integer and string.

Run-time Polymorphism (Dynamic Binding or Late Binding):

Method Overriding: Occurs when a subclass provides a specific implementation of a method that is already defined in its superclass or parent class . The subclass or child class method overrides the superclass method with the same signature and same return type.

using System;

// Base class
class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a shape");
}
}

// Derived class
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}

class Program
{
static void Main(string[] args)
{
// Runtime binding example
Shape shape = new Circle(); // Creating instance of derived class but of base class reference
shape.Draw(); // This will call the overridden method in Circle class at runtime
}
}

Method Overriding:

  • In this example, the Shape class defines a virtual method Draw().
  • The Circle class inherits from Shape and overrides the Draw() method with its own implementation.
  • Method overriding occurs when a subclass provides a specific implementation of a method that is already defined in its superclass (Shape).
  • The Draw() method in Circle overrides the Draw() method in Shape with the same signature and return type.

Runtime Binding:

  • In the Main method, an instance of Circle is created and assigned to a reference of type Shape.
  • This is an example of polymorphism (subtype polymorphism) Later Section i will explain this, where a derived class (Circle) object can be treated as its base class (Shape) type.
  • At runtime, when shape.Draw() is called, the C# runtime system determines the actual type of the object (Circle) and executes the overridden method in Circle.
  • This is known as runtime binding or late binding, where the decision of which method to call is made during runtime based on the actual type of the object.

Parametric Polymorphism (Generic Programming):

Generics: Allows writing functions or classes that can operate on a wide range of data types without knowing the specific type at compile time. It provides flexibility and code reusability by abstracting over types.

using System;

public class Stack<T>
{
private T[] elements;
private int top;

public Stack(int size)
{
elements = new T[size];
top = -1;
}

public void Push(T item)
{
if (top == elements.Length - 1)
{
Console.WriteLine("Stack overflow!");
return;
}
elements[++top] = item;
}

public T Pop()
{
if (top == -1)
{
Console.WriteLine("Stack underflow!");
return default(T);
}
return elements[top--];
}

public void Print()
{
Console.WriteLine("Stack elements:");
for (int i = top; i >= 0; i--)
{
Console.WriteLine(elements[i]);
}
}
}

class Program
{
static void Main(string[] args)
{
Stack<int> intStack = new Stack<int>(5);
intStack.Push(1);
intStack.Push(2);
intStack.Push(3);
intStack.Print();
Console.WriteLine("Popped item: " + intStack.Pop());

Stack<string> stringStack = new Stack<string>(3);
stringStack.Push("Hello");
stringStack.Push("World");
stringStack.Print();
Console.WriteLine("Popped item: " + stringStack.Pop());
}
}

In this example:

  • We define a Stack<T> class that uses a generic type parameter T.
  • The Stack<T> class can work with any data type specified when creating an instance of the class.
  • We demonstrate the use of the Stack<T> class with both int and string types.
  • By using generics, the Stack class provides flexibility and code reusability by allowing the same implementation to work with different data types.

Subtype Polymorphism (Inclusion Polymorphism):

Method Overriding: Enables objects of a derived class to be treated as objects of their base class, allowing polymorphic behavior. It occurs when a derived class inherits from a base class and provides its own implementation of certain methods, thereby overriding the behavior of the base class methods.

using System;

// Interface
public interface IShape
{
void Draw();
}

// Class implementing the interface
public class Circle : IShape
{
public void Draw()
{
Console.WriteLine("Drawing a circle");
}
}

// Another class implementing the interface
public class Rectangle : IShape
{
public void Draw()
{
Console.WriteLine("Drawing a rectangle");
}
}

class Program
{
static void Main(string[] args)
{
IShape shape1 = new Circle();
IShape shape2 = new Rectangle();

shape1.Draw(); // Calls the Draw method of Circle
shape2.Draw(); // Calls the Draw method of Rectangle
}
}
  • The Circle class and Rectangle class both implement the IShape interface and provide their own implementation of the Draw method.
  • At runtime, we create objects of Circle and Rectangle classes and assign them to variables of type IShape.

Most of the design pattern use subtype polymorphism to implement flexible and robust class design, this is a very powerful polymorphism behavior like strategy design pattern or observer design pattern or decorator design pattern etc.

Coercion polymorphism (Casting)

Coercion polymorphism, also known as implicit conversion, refers to the automatic conversion of one data type to another by the compiler or runtime environment. This conversion occurs when it is safe and logical to do so, allowing for more flexible and convenient programming.

using System;

class Program
{
static void Main(string[] args)
{
int num1 = 10;
double num2 = num1; // Implicit conversion from int to double

Console.WriteLine("num1: " + num1); // Outputs: num1: 10
Console.WriteLine("num2: " + num2); // Outputs: num2: 10.0
}
}

In this example:

  • We have an integer variable num1 initialized with the value 10.
  • We assign the value of num1 to a double variable num2. This assignment triggers an implicit conversion from int to double.
  • The compiler automatically converts the int value to a double value, as it is safe to do so without losing precision.
  • This implicit conversion allows us to treat num2 as a double even though it was originally an int, demonstrating coercion polymorphism.

SUMMARY

Polymorphism in object-oriented programming encompasses compile-time and runtime behaviors. Compile-time polymorphism involves method and operator overloading, allowing multiple behaviors based on parameters and operators. Runtime polymorphism, achieved through method overriding, enables a subclass to provide its own implementation of a superclass method. Additional forms include parametric polymorphism with generics, subtype polymorphism through method overriding and interfaces, and coercion polymorphism for implicit type conversions. These concepts facilitate code flexibility, reusability, and adaptability in software development.

--

--