;

C# Polymorphism


Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP), alongside inheritance, encapsulation, and abstraction. In C#, polymorphism allows objects of different types to be treated as instances of the same type through a unified interface. It provides flexibility and extensibility in your code by enabling a single interface to handle multiple types.

In this tutorial, we will explore C# polymorphism in detail, including how it works, the types of polymorphism, use cases, examples and real-world scenario.

What is Polymorphism in C#?

Polymorphism in C# means "many forms". It allows objects to be accessed through references of their base class type, enabling a single method, property, or operator to work in different ways depending on the context. Polymorphism enables developers to write more generic and reusable code, where the behavior of methods can vary based on the object that invokes them.

In simpler terms, polymorphism allows for "one interface, many implementations". This means a base class can define a method, and derived classes can override or provide different implementations of that method while still using the same interface or method signature.

Types of Polymorphism

Compile-Time Polymorphism (Static)

Compile-time polymorphism is achieved through method overloading or operator overloading. It is called static because the decision of which method to invoke is made at compile-time.

  • Method Overloading: When a class has multiple methods with the same name but different parameters (number or type), this is known as method overloading.
  • Operator Overloading: Defining how operators work with user-defined types is known as operator overloading.

Example of Compile-Time Polymorphism

class Calculator
{
    // Method Overloading: Add method with different parameter types
    public int Add(int a, int b)
    {
        return a + b;
    }

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

class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();

        Console.WriteLine(calculator.Add(5, 10));   // Output: 15 (int version)
        Console.WriteLine(calculator.Add(5.5, 4.5)); // Output: 10.0 (double version)
    }
}

In the above example, the Add method is overloaded to accept both int and double types. The correct method is chosen at compile-time based on the arguments provided.

Run-Time Polymorphism (Dynamic)

Run-time polymorphism is achieved through method overriding using inheritance. It is called dynamic because the decision of which method to call is made at runtime.

To implement run-time polymorphism in C#, you typically use:

  • Inheritance: A derived class inherits from a base class.
  • Virtual Methods: Methods in the base class that can be overridden by derived classes using the virtual and override keywords.

Example of Run-Time Polymorphism

class Animal
{
    // Virtual method that can be overridden
    public virtual void Speak()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

class Dog : Animal
{
    // Overriding the Speak method for Dog
    public override void Speak()
    {
        Console.WriteLine("The dog barks.");
    }
}

class Cat : Animal
{
    // Overriding the Speak method for Cat
    public override void Speak()
    {
        Console.WriteLine("The cat meows.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Using base class reference to refer to derived class objects
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        myDog.Speak(); // Output: The dog barks.
        myCat.Speak(); // Output: The cat meows.
    }
}

Here, the base class Animal has a virtual method Speak, and derived classes (Dog and Cat) override this method to provide their specific implementations. The method call is resolved at runtime based on the object type.

Examples of Polymorphism in C#

Example 1: Compile-Time Polymorphism (Method Overloading)

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

    // Method overloading with different parameter types
    public void Draw(string shapeName)
    {
        Console.WriteLine($"Drawing a {shapeName}.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Shape shape = new Shape();
        shape.Draw(); // Output: Drawing a shape.
        shape.Draw("Circle"); // Output: Drawing a Circle.
    }
}

Example 2: Run-Time Polymorphism (Method Overriding)

class Employee
{
    public virtual void Work()
    {
        Console.WriteLine("Employee is working.");
    }
}

class Manager : Employee
{
    public override void Work()
    {
        Console.WriteLine("Manager is managing the team.");
    }
}

class Developer : Employee
{
    public override void Work()
    {
        Console.WriteLine("Developer is writing code.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Employee emp1 = new Manager();
        Employee emp2 = new Developer();

        emp1.Work();  // Output: Manager is managing the team.
        emp2.Work();  // Output: Developer is writing code.
    }
}

Real-World Example

Let’s take a real-world example of a Payment System where polymorphism is applied to handle different payment methods.

class Payment
{
    public virtual void MakePayment()
    {
        Console.WriteLine("Making payment.");
    }
}

class CreditCardPayment : Payment
{
    public override void MakePayment()
    {
        Console.WriteLine("Processing credit card payment.");
    }
}

class PayPalPayment : Payment
{
    public override void MakePayment()
    {
        Console.WriteLine("Processing PayPal payment.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Payment payment;

        // Process a credit card payment
        payment = new CreditCardPayment();
        payment.MakePayment(); // Output: Processing credit card payment.

        // Process a PayPal payment
        payment = new PayPalPayment();
        payment.MakePayment(); // Output: Processing PayPal payment.
    }
}

Explanation:

In this example:

  • We have a base class Payment that defines a general MakePayment method.
  • We have two derived classes, CreditCardPayment and PayPalPayment, which override the base method to provide their specific implementations for processing payments.
  • At runtime, the appropriate method is called depending on the object type, whether it’s a CreditCardPayment or a PayPalPayment.

This approach makes the system extensible. If a new payment method (like BitcoinPayment) is introduced, we can simply extend the base class and override the MakePayment method without affecting existing code.

Key Takeaways

  1. Compile-Time Polymorphism: Achieved using method overloading or operator overloading, where the method call is resolved at compile-time.
  2. Run-Time Polymorphism: Achieved using inheritance and method overriding, where the method call is resolved at runtime based on the object’s type.
  3. Flexibility: Polymorphism allows for more flexible and reusable code, enabling developers to write generic methods that work with different types of objects.
  4. Extensibility: Polymorphism makes it easy to extend systems, as new functionality can be added without modifying existing code.
  5. Unified Interface: Polymorphism allows the use of a common interface (e.g., a base class) to interact with different derived types, making the code easier to maintain and understand.

Summary

Polymorphism in C# is a powerful OOP concept that enables a single interface to represent different types. It is classified into compile-time (method overloading and operator overloading) and run-time polymorphism (method overriding using inheritance). By allowing different behaviors to be associated with the same method, polymorphism promotes flexibility, code reusability, and extensibility.

In real-world applications, polymorphism is commonly used in systems where different entities share common behaviors but may have unique implementations, such as payment processing, shape drawing, and employee task management. By mastering polymorphism, you can write more efficient, maintainable, and scalable applications in C#.