Polymorphism in C#

Polymorphism, meaning "many forms," is a fundamental concept in object-oriented programming that allows you to treat objects of different classes in a uniform way. In C#, polymorphism is primarily achieved through inheritance and interfaces, enabling methods to behave differently based on the type of object they are called on.

Types of Polymorphism in C#

C# supports two main types of polymorphism:

Run-time Polymorphism (Method Overriding)

Run-time polymorphism is the more commonly discussed type when referring to the "polymorphism" concept in OOP. It enables a derived class to provide a specific implementation of a method that is already provided by its base class.

Key Concepts:

Example: Shape Hierarchy

Consider a base class Shape with a virtual method Draw(). Derived classes like Circle and Square can override this method to provide their specific drawing logic.

C#

// Base class
public abstract class Shape
{
    // Virtual method that can be overridden
    public virtual void Draw()
    {
        Console.WriteLine("Drawing a generic shape.");
    }
}

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

// Derived class 2
public class Square : Shape
{
    // Overriding the virtual method
    public override void Draw()
    {
        Console.WriteLine("Drawing a square.");
    }
}

// Example usage
public class Program
{
    public static void Main(string[] args)
    {
        Shape myCircle = new Circle();
        Shape mySquare = new Square();
        Shape genericShape = new Shape(); // If Shape were not abstract

        myCircle.Draw(); // Output: Drawing a circle.
        mySquare.Draw(); // Output: Drawing a square.
        // genericShape.Draw(); // Would output: Drawing a generic shape.
    }
}
                

In this example, even though we are calling Draw() on variables of type Shape, the actual method executed depends on the concrete type of the object (Circle or Square) at runtime.

Abstract Classes and Methods

Abstract classes are classes that cannot be instantiated directly. They are designed to be inherited from. Abstract methods are methods declared in an abstract class that do not have an implementation. Derived classes must provide an implementation for all abstract methods.

Example: Abstract Base Class

Using the Shape example, if we make it abstract and the Draw() method abstract, all derived classes will be forced to implement it.

C#

// Abstract base class
public abstract class AbstractShape
{
    // Abstract method (no implementation)
    public abstract void Draw();
}

// Derived class 1
public class Triangle : AbstractShape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a triangle.");
    }
}

// Derived class 2
public class Rectangle : AbstractShape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a rectangle.");
    }
}

// Example usage
public class Program
{
    public static void Main(string[] args)
    {
        // AbstractShape shape = new AbstractShape(); // ERROR: Cannot create an instance of an abstract class.

        AbstractShape myTriangle = new Triangle();
        AbstractShape myRectangle = new Rectangle();

        myTriangle.Draw(); // Output: Drawing a triangle.
        myRectangle.Draw(); // Output: Drawing a rectangle.
    }
}
                

This enforces a contract: any class inheriting from AbstractShape must define its own Draw() behavior.

Note: When using virtual, the base class provides a default implementation. When using abstract, there is no default implementation; derived classes must provide one.

Compile-time Polymorphism (Method Overloading)

Method overloading allows you to define multiple methods in the same class with the same name but different parameter lists (different number of parameters, types of parameters, or order of parameters). The compiler determines which method to call based on the arguments provided.

Example: Overloaded Add Method

C#

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

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

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

// Example usage
public class Program
{
    public static void Main(string[] args)
    {
        Calculator calc = new Calculator();

        Console.WriteLine(calc.Add(5, 10));         // Calls Add(int, int) -> Output: 15
        Console.WriteLine(calc.Add(3.5, 2.1));     // Calls Add(double, double) -> Output: 5.6
        Console.WriteLine(calc.Add(1, 2, 3));      // Calls Add(int, int, int) -> Output: 6
    }
}
                
Tip: While method overloading is technically a form of polymorphism, the term "polymorphism" in OOP discussions usually refers to the run-time (dynamic) polymorphism enabled by inheritance and overriding.

Benefits of Polymorphism

Understanding and effectively using polymorphism is crucial for developing robust, scalable, and maintainable object-oriented applications in C#.