Polymorphism Concept in Object-Oriented Programming

Polymorphism in general life

In general terms, polymorphism refers to the ability of a single entity to take multiple forms or exhibit multiple behaviors. It is a concept that can be observed in various aspects of our everyday lives, not just limited to programming. Let's explore some real-life examples to understand the essence of polymorphism.

For e.g Electrical Outlets

Consider electrical outlets in your home. You can plug in various devices like a laptop, smartphone, or a vacuum cleaner into the same outlet. The outlet serves a common interface for different devices, allowing them to function despite their diverse characteristics. This is akin to polymorphism, where a single entity (electrical outlet) supports multiple forms (devices with different power requirements).

Polymorphism in object-orinented programming

Polymorphism is a feature of object-oriented programming that allows an entity (such as a method or an operator) to have different forms or behaviors depending on the context. The word polymorphism means “many forms” in Greek. Polymorphism can be achieved in C# through two ways: compile-time polymorphism and run-time polymorphism.

Compile-time polymorphism

Compile-time polymorphism, also known as static polymorphism, is when the compiler determines which method or operator to invoke at compile time, hence it is also called as early binding where compiler knows which method of class to call at compile time only. Here compiler decide which method to call using class of object and method or operator and its signature (number of argument, and type of arguments). Here please note that return type of method cannot be considered for mthod overloading, if two method has exact same signature and different return type, it's not considered as method overriding and compiler generates error message for same.

Method overloading is when you have multiple methods with the same name but different parameters in a class. For e.g. in below example Add method in Calculator class is overloaded to accept two integer type of argument in first place and in second place it is accepting three parameter of double type.

class Calculator
{
    // Method with two integer parameters
    public int Add(int a, int b)
    {
        return a + b;
    }

    // Method with three double parameters
    public double Add(double a, double b, double c)
    {
        return a + b + c;
    }
}

class Program
{
    static void Main()
    {
        Calculator calculator = new Calculator();

        // Calls the first Add method
        int result1 = calculator.Add(5, 10);

        // Calls the second Add method
        double result2 = calculator.Add(2.5, 3.5, 4.0);

        Console.WriteLine($"Result1: {result1}");
        Console.WriteLine($"Result2: {result2}");
    }
}

Output

Error list

If we try to add another Add method with same signature with different return type compiler gives error, as its not allowed

Operator overloading is when you redefine the behavior of an operator for a custom type. For example, you can overload the + operator to perform string concatenation or matrix addition.

Run-time polymorphism

Run-time polymorphism, also known as dynamic polymorphism, is when the method or operator to invoke is determined at run time based on the type of the object. This can be done by using inheritance and virtual methods. Inheritance is when you create a new class from an existing class and inherit its members. Virtual methods are methods that can be overridden by derived classes to provide different implementations. For example, you can have a virtual method called Draw() in a base class called Shape, and override it in derived classes such as Circle, Rectangle, and Triangle.

using System;

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

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

// derived class
class Rectangle : Shape
{
    // override method
    public override void Draw()
    {
        Console.WriteLine("Drawing a rectangle");
    }
}

// derived class
class Triangle : Shape
{
    // override method
    public override void Draw()
    {
        Console.WriteLine("Drawing a triangle");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // create an array of Shape objects
        Shape[] shapes = new Shape[4];
        shapes[0] = new Shape();
        shapes[1] = new Circle();
        shapes[2] = new Rectangle();
        shapes[3] = new Triangle();

        // loop through the array and call the Draw method
        foreach (Shape shape in shapes)
        {
            shape.Draw();
        }
    }
}

Here is an example of polymorphism using inheritance and virtual methods in C#.

Drawing a shape

Drawing a circle

Drawing a rectangle

Drawing a triangle

Polymorphism is deeply ingrained within the .NET Framework. Here's an example demonstrating polymorphism using the Stream class and its derived classes.

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        // Create a FileStream object, but assign it to a Stream variable
        Stream stream = new FileStream("example.txt", FileMode.Open);

        // Read from the stream
        byte[] buffer = new byte[1024];
        int bytesRead = stream.Read(buffer, 0, buffer.Length);

        // Convert the bytes read to string and display
        Console.WriteLine("Data read from the file:");
        Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, bytesRead));

        // Close the stream
        stream.Close();
    }
}

In this example, we are using polymorphism through the Stream class and its derived class FileStream. We declare a Stream variable and instantiate it with a FileStream object. This is possible because FileStream inherits from Stream, allowing us to treat a FileStream object as a Stream object. Later, we use the Read method of the Stream class to read data from the file. At runtime, the Read method behaves differently based on the actual type of the object (FileStream in this case), demonstrating polymorphic behavior.

Let's assume "example.txt" contains the following text.

"Hello, this is an example file for demonstrating polymorphism in .NET."

Then the output of the program will be:

Data read from the file:

"Hello, this is an example file for demonstrating polymorphism in .NET."

Summary

Polymorphism means one name multiple forms or one function name doing different thing as per context (i.e. number of parameters or type of parameters), types of polymorphism are compile time polymorphism and runtime polymorphism. Compile time polymorphism or early binding is achieved by doing function overloading and operator overloading. Runtime polymorphsim is achieved using function overriding, base class with virtual function and derived class overriding it.