Skip to content

Polymorphism

Polymorphism is a fundamental concept in object-oriented programming (OOP) that allows objects of different types to be treated as objects of a common base type. In C#, polymorphism is achieved through mechanisms like method overriding and interfaces. There are two main types of polymorphism: compile-time polymorphism (also known as static or early binding) and runtime polymorphism (also known as dynamic or late binding).

Compile-Time Polymorphism:

Method Overloading:

Compile-time polymorphism is achieved through method overloading, where multiple methods in the same class have the same name but different parameter lists. The appropriate method is chosen at compile-time based on the method signature.

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

    // Overloaded method with two double parameters
    public double Add(double a, double b)
    {
        return a + b;
    }
}
C#
class Program
{
    static void Main()
    {
        MathOperations math = new MathOperations();

        int result1 = math.Add(2, 3);         // Calls the first Add method
        double result2 = math.Add(2.5, 3.5); // Calls the second Add method
    }
}

Runtime Polymorphism

Runtime polymorphism is achieved through method overriding, where a method in a base class is marked as virtual and can be overridden by a method in a derived class using the override keyword. This type of polymorphism is often associated with upcasting, which means a variable of a base type references an instance of a derived type.

C#
public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Some generic sound");
    }
}
C#
public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Woof! Woof!");
    }
}
C#
public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Meow!");
    }
}
C#
class Program
{
    static void Main()
    {
        Animal myAnimal = new Dog();

        // Calls the overridden MakeSound method in Dog
        myAnimal.MakeSound(); 

        myAnimal = new Cat();

        // Calls the overridden MakeSound method in Cat
        myAnimal.MakeSound();
    }
}

In this example, the myAnimal variable is declared as the base type Animal, and references instances of its derived types. The MakeSound method is marked as virtual in the Animal class and overridden in the Dog and Cat classes. At runtime, the appropriate version of the method is called based on the actual type of the object.

When a variable declared as a base type references an instance of a derived type, the reference can only be used to invoke behaviors defined within the base type. Any unique behavior defined in a derived type will not be accessible. For example, if the Cat class had a behavior Purr, invoking the behavior with the myAnimal variable would cause a build error.

C#
class Program
{
    static void Main()
    {
        Animal myAnimal = new Cat();

        // This statement will cause a compile error
        myAnimal.Purr();
    }
}

When the above code is compiled, the variable myAnimal is only known to be an Animal. Therefore, the statement is an error because Animal's don't Purr. In order to make this code work, it would need to be determined that the Animal is actual a type of animal that can Purr.

C#
class Program
{
    static void Main()
    {
        Animal myAnimal = new Cat();

        // Determine if the object is a Cat
        if (myAnimal is Cat)
        {
            // Type cast object reference
            Cat cat = (Cat)myAnimal;
            cat.Purr();
        }        
    }
}

In this example, a selection is performed to determine the type of the Animal. This is done so you don't accidentally try to type cast a different type of Animal to a Cat. Inside the selection block, a type cast gets an object reference as the Cat type, so that the Purr behavior can be invoked.

Polymorphism in C# provides flexibility and allows for the creation of code that is more generic, extensible, and maintainable. It allows you to work with objects at a higher level of abstraction, treating them based on their common characteristics rather than their specific types.