Paramclasses Academy - Blog

Tutorials Details

←
Post 16 of 30
→

What is Polymorphism in C# ? Explain Method Overloading vs Method Overriding

What is Polymorphism in C# ? Explain Method Overloading vs Method Overriding

October 26, 2026

Introduction

Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP) in C#. The word polymorphism means "many forms." In programming, it allows the same method name or common interface to represent different behaviors depending on the arguments supplied or the actual type of an object.

For example, a calculator can use an Add() method to add two integers or three integers. Similarly, an Animal reference can call MakeSound() on different objects, such as a Dog or a Cat, and each object can provide its own implementation.

C# supports polymorphism through several mechanisms, especially method overloading and method overriding.

In this tutorial, you will learn the difference between compile-time and runtime polymorphism, how to overload and override methods, how the virtual, override, and new keywords work, and how to implement polymorphism in practical applications.

What You Will Learn

  • What polymorphism means in C#

  • Compile-time vs runtime polymorphism

  • Method overloading with different parameter lists

  • Method overriding with inheritance

  • The virtual, override, and base keywords

  • Method hiding with new

  • Polymorphism using base-class references

  • Abstract classes and interfaces

  • Practical examples and common mistakes

  • Best practices for designing polymorphic applications

1. What Is Polymorphism in C#?

Polymorphism is an OOP concept that allows one method name or common abstraction to represent multiple behaviors.

Consider a Shape class hierarchy. A circle and a rectangle both have an area, but the formula used to calculate the area differs.

  • A circle uses (\pi r^2).

  • A rectangle uses width multiplied by height.

With polymorphism, both shapes can expose a common CalculateArea() method while providing their own calculations.

Main Types of Polymorphism

Type Also known as Common C# mechanism
Compile-time polymorphism Static polymorphism Method overloading
Runtime polymorphism Dynamic polymorphism Method overriding and interface dispatch

Method overloading is resolved by the compiler using the method name, argument count, argument types, and applicable overload-resolution rules. Runtime polymorphism allows an overridden implementation to be selected according to the actual object type.

2. What Is Method Overloading in C#?

Method overloading means declaring multiple methods with the same name in the same type, but with different parameter lists.

Overloaded methods can accept different numbers or types of arguments, making an API easier to use for different scenarios.

Example: Method Overloading

using System;

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

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

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

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

        Console.WriteLine(calculator.Add(10, 20));
        Console.WriteLine(calculator.Add(10, 20, 30));
        Console.WriteLine(calculator.Add(5.5, 4.5));
    }
}

Output:

30
60
10

How This Example Works

The Calculator class contains three methods named Add():

  • Add(int, int) accepts two integers.

  • Add(int, int, int) accepts three integers.

  • Add(double, double) accepts two floating-point numbers.

The compiler selects the applicable overload based on the arguments supplied.

Benefits of Method Overloading

  • Improves API readability.

  • Allows one meaningful method name to support different input forms.

  • Reduces the need to invent separate names such as AddTwoNumbers() and AddThreeNumbers().

  • Makes classes easier to use when several related operations share the same purpose.

3. Rules of Method Overloading

C# allows method overloading when methods have different parameter signatures.

Overloading by Number of Parameters

class DisplayService
{
    public void Show(string message)
    {
        Console.WriteLine(message);
    }

    public void Show(string title, string message)
    {
        Console.WriteLine($"{title}: {message}");
    }
}

The methods differ in the number of parameters.

Overloading by Parameter Types

class Printer
{
    public void Print(int number)
    {
        Console.WriteLine($"Integer: {number}");
    }

    public void Print(string text)
    {
        Console.WriteLine($"Text: {text}");
    }
}

The methods have the same name and parameter count, but their parameter types differ.

Can Methods Be Overloaded by Return Type Only?

No. The return type alone does not distinguish method overloads.

This is invalid:

// Invalid: return type alone cannot distinguish overloads.
// int Calculate(int value) { return value; }
// double Calculate(int value) { return value; }

To create valid overloads, change the parameter list in a way that creates a distinct signature.

Also, changing only parameter names or adding ref versus out to otherwise identical parameter types does not provide a valid way to distinguish overloads. C# has specific signature rules for these cases.

4. Practical Example of Method Overloading

Let's create a billing calculator that supports different input combinations.

using System;

class BillingCalculator
{
    public decimal CalculateTotal(decimal price)
    {
        return price;
    }

    public decimal CalculateTotal(decimal price, int quantity)
    {
        return price * quantity;
    }

    public decimal CalculateTotal(
        decimal price,
        int quantity,
        decimal discount)
    {
        if (discount < 0 || discount > 100)
        {
            throw new ArgumentOutOfRangeException(
                nameof(discount));
        }

        decimal subtotal = price * quantity;
        return subtotal - subtotal * discount / 100;
    }
}

class Program
{
    static void Main()
    {
        BillingCalculator billing = new BillingCalculator();

        Console.WriteLine(billing.CalculateTotal(100m));
        Console.WriteLine(billing.CalculateTotal(100m, 3));
        Console.WriteLine(billing.CalculateTotal(100m, 3, 10m));
    }
}

Output:

100
300
270

The same method name handles a single item price, a quantity-based total, or a discounted total.

In a real billing system, you would also consider currency precision, taxes, rounding rules, and validation of prices and quantities.

5. What Is Method Overriding in C#?

Method overriding allows a derived class to provide its own implementation of an eligible inherited method.

In C#, the base-class method must be declared virtual, abstract, or already override for a derived class to override it.

The derived implementation uses the override keyword.

Example: Method Overriding

using System;

class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("The dog barks.");
    }
}

class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("The cat meows.");
    }
}

class Program
{
    static void Main()
    {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.MakeSound();
        animal2.MakeSound();
    }
}

Output:

The dog barks.
The cat meows.

How It Works

  • Animal declares MakeSound() as virtual.

  • Dog overrides the method with its own behavior.

  • Cat also overrides the method.

  • Both variables are declared as Animal, but they refer to different derived objects.

  • At runtime, the appropriate overridden implementation is invoked.

This is an example of runtime polymorphism.

6. Understanding virtual, override, and base

These keywords play important roles in inheritance-based polymorphism.

The virtual Keyword

The virtual keyword marks an eligible instance method or property accessor as overridable.

class Vehicle
{
    public virtual void Start()
    {
        Console.WriteLine("Vehicle is starting.");
    }
}

A derived class can override Start() to provide specialized behavior.

The override Keyword

The override keyword provides a replacement implementation for an inherited virtual or abstract member.

class Car : Vehicle
{
    public override void Start()
    {
        Console.WriteLine("Car engine is starting.");
    }
}

The base Keyword

The base keyword allows a derived class to call the base-class implementation.

class Car : Vehicle
{
    public override void Start()
    {
        base.Start();
        Console.WriteLine("Car engine is starting.");
    }
}

When Start() is called on a Car object, the base implementation runs first, followed by the additional message.

Output:

Vehicle is starting.
Car engine is starting.

Use base when you need to extend the inherited behavior rather than completely replace it.

7. Method Overloading vs Method Overriding

These two concepts are related to polymorphism, but they solve different problems.

Method Overloading Method Overriding
Uses the same method name with different parameter lists. Redefines an eligible inherited method.
Commonly used within the same type. Requires inheritance for class-based overriding.
The compiler resolves the applicable overload. Runtime dispatch selects the overridden implementation for a virtual call.
Does not require virtual or override. Uses override in the derived class.
Useful for supporting different input forms. Useful for specialized behavior in derived types.
Example: Add(int, int) and Add(double, double). Example: Dog.MakeSound() and Cat.MakeSound().

A simple way to remember the difference:

  • Overloading: Same method name, different parameter lists.

  • Overriding: Same inherited virtual member, different implementation in a derived class.

8. Compile-Time vs Runtime Polymorphism

Compile-Time Polymorphism

With method overloading, the compiler resolves which method to call based on the available overloads and the compile-time argument information.

class Calculator
{
    public int Multiply(int a, int b)
    {
        return a * b;
    }

    public double Multiply(double a, double b)
    {
        return a * b;
    }
}

The compiler selects the applicable Multiply() overload for the arguments.

Runtime Polymorphism

With method overriding, a virtual call can execute the implementation provided by the actual runtime type.

Animal animal = new Dog();
animal.MakeSound();

Although the variable's declared type is Animal, the object is a Dog, so the overridden Dog.MakeSound() method runs.

Runtime polymorphism is especially useful when code needs to work with a family of related types without checking every concrete type manually.

9. Method Hiding with the new Keyword

C# also allows a derived class to hide an inherited member using the new keyword. Method hiding is different from overriding.

using System;

class Parent
{
    public void Show()
    {
        Console.WriteLine("Parent Show()");
    }
}

class Child : Parent
{
    public new void Show()
    {
        Console.WriteLine("Child Show()");
    }
}

class Program
{
    static void Main()
    {
        Parent first = new Child();
        Child second = new Child();

        first.Show();
        second.Show();
    }
}

Output:

Parent Show()
Child Show()

The method selected depends on the variable's compile-time type because Show() is hidden rather than overridden.

If the intention is runtime polymorphism, a virtual method in the base class and an override method in the derived class are usually the appropriate choice.

The new keyword suppresses the warning that would otherwise indicate that a member hides an inherited member.

10. Polymorphism with Abstract Classes

Abstract classes are useful when related types should share a common contract but must provide their own implementations of specific operations.

using System;

abstract class Shape
{
    public abstract double CalculateArea();
}

class Circle : Shape
{
    public double Radius { get; }

    public Circle(double radius)
    {
        if (radius < 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(radius));
        }

        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }

    public Rectangle(double width, double height)
    {
        if (width < 0 || height < 0)
        {
            throw new ArgumentOutOfRangeException(
                "Dimensions cannot be negative.");
        }

        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

class Program
{
    static void Main()
    {
        Shape shape1 = new Circle(5);
        Shape shape2 = new Rectangle(4, 6);

        Console.WriteLine(shape1.CalculateArea());
        Console.WriteLine(shape2.CalculateArea());
    }
}

Output:

78.53981633974483
24

Shape defines the required operation, CalculateArea(). Each concrete shape implements the calculation appropriate to its geometry.

The calling code can use the Shape abstraction without needing to know the exact concrete type.

11. Polymorphism Using Interfaces

Interfaces allow unrelated classes to provide a common capability. A method can accept an interface and work with any object that implements it.

using System;

interface INotification
{
    void Send(string message);
}

class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

class SmsNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine($"SMS sent: {message}");
    }
}

class NotificationService
{
    public void Notify(INotification notification, string message)
    {
        notification.Send(message);
    }
}

class Program
{
    static void Main()
    {
        NotificationService service = new NotificationService();

        service.Notify(
            new EmailNotification(), "Welcome!");

        service.Notify(
            new SmsNotification(), "Your code is 1234.");
    }
}

Output:

Email sent: Welcome!
SMS sent: Your code is 1234.

The NotificationService depends on the INotification interface rather than a particular implementation.

This makes it easier to add new notification types without changing the service's core logic.

12. Practical Project: Payment Processing System

Let's create a simple payment processing example using runtime polymorphism.

Different payment methods can share a common abstract base class while implementing their own payment behavior.

using System;
using System.Collections.Generic;

abstract class PaymentMethod
{
    public abstract string MethodName { get; }

    public abstract void Pay(decimal amount);
}

class CreditCardPayment : PaymentMethod
{
    public override string MethodName => "Credit Card";

    public override void Pay(decimal amount)
    {
        Console.WriteLine(
            $"Processing credit card payment: {amount:C}");
    }
}

class UpiPayment : PaymentMethod
{
    public override string MethodName => "UPI";

    public override void Pay(decimal amount)
    {
        Console.WriteLine(
            $"Processing UPI payment: {amount:C}");
    }
}

class CashPayment : PaymentMethod
{
    public override string MethodName => "Cash";

    public override void Pay(decimal amount)
    {
        Console.WriteLine(
            $"Processing cash payment: {amount:C}");
    }
}

class PaymentService
{
    public void ProcessPayment(
        PaymentMethod paymentMethod,
        decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(amount), "Amount must be positive.");
        }

        Console.WriteLine(
            $"Payment Method: {paymentMethod.MethodName}");

        paymentMethod.Pay(amount);
    }
}

class Program
{
    static void Main()
    {
        PaymentService service = new PaymentService();

        List<PaymentMethod> methods = new List<PaymentMethod>
        {
            new CreditCardPayment(),
            new UpiPayment(),
            new CashPayment()
        };

        foreach (PaymentMethod method in methods)
        {
            service.ProcessPayment(method, 1500m);
            Console.WriteLine("--------------------");
        }
    }
}

Example output (currency formatting depends on the system's culture):

Payment Method: Credit Card
Processing credit card payment: ₹1,500.00
--------------------
Payment Method: UPI
Processing UPI payment: ₹1,500.00
--------------------
Payment Method: Cash
Processing cash payment: ₹1,500.00
--------------------

How This Project Works

  1. PaymentMethod defines the common abstraction.

  2. CreditCardPayment, UpiPayment, and CashPayment implement their own payment behavior.

  3. PaymentService accepts a PaymentMethod rather than depending on one concrete class.

  4. A list stores multiple payment implementations.

  5. Each call to Pay() invokes the appropriate override for the actual object.

This is a simplified learning example. A production payment system would need secure payment-provider integration, transaction verification, error handling, logging, and compliance controls.

13. Common Mistakes in Polymorphism

Mistake 1: Trying to override a non-virtual method

Incorrect:

class Parent
{
    public void Display()
    {
        Console.WriteLine("Parent");
    }
}

class Child : Parent
{
    // This does not override Parent.Display():
    // public override void Display() { }
}

To enable overriding, declare the base method virtual or abstract, or override an eligible inherited virtual member.

Mistake 2: Overloading methods by return type alone

Two methods cannot be distinguished solely by their return types.

Use different parameter lists instead.

Mistake 3: Confusing method hiding with overriding

The new keyword hides a member. The override keyword participates in virtual dispatch.

Use the keyword that matches the intended behavior.

Mistake 4: Using excessive type checks

Code that repeatedly checks concrete types and casts objects may become difficult to maintain. Consider whether a shared base class or interface would allow polymorphic method calls instead.

Mistake 5: Creating unrelated overrides

An override should preserve the intended meaning of the base member. A derived class should not introduce behavior that violates the expectations of code using the base abstraction.

Mistake 6: Forgetting input validation

Polymorphism does not remove the need to validate input. Each implementation should handle invalid values according to the requirements of the application.

14. Best Practices for Polymorphism in C#

  1. Use overloading for related input variations. Keep the method name consistent when the operations share the same purpose.

  2. Use overriding for specialized behavior. Make the base class contract clear and predictable.

  3. Prefer interfaces for shared capabilities. Interfaces can reduce unnecessary coupling to class hierarchies.

  4. Use abstract classes when shared implementation is useful. They can combine common code with required derived behavior.

  5. Avoid confusing method hiding. Use new deliberately, not as a substitute for override.

  6. Keep method contracts consistent. Derived classes should preserve the expectations established by their base type.

  7. Favor dependency inversion. Where appropriate, make services depend on abstractions instead of concrete implementations.

  8. Validate method arguments. Do not assume that polymorphic calls always receive valid input.

  9. Use meaningful method names. Overloading should improve usability rather than create ambiguity.

  10. Test each implementation. Verify both the common contract and the specific behavior of every implementation.

15. Practice Exercises

Exercise 1: Calculator Overloading

Create a Calculator class with overloaded Add() methods for two integers, three integers, and two decimal values.

Exercise 2: Animal Overriding

Create an Animal base class with a virtual MakeSound() method. Override it in Dog, Cat, and Cow.

Exercise 3: Shape Polymorphism

Create an abstract Shape class with CalculateArea(). Implement Circle, Rectangle, and Triangle.

Exercise 4: Notification System

Create an INotification interface and implement it using email, SMS, and push-notification classes.

Exercise 5: Employee Salary

Create an abstract Employee class with a salary calculation method. Implement different salary rules for full-time and part-time employees.

Exercise 6: Overloading vs Hiding

Create a base class and derived class with methods of the same name. Test the difference between method hiding with new and method overriding with virtual and override.

16. Knowledge Check: Quiz

1. What does polymorphism mean?

A. One method or abstraction can represent different behaviors
B. A class cannot contain methods
C. A variable must be constant
D. A loop executes only once

2. Which mechanism is commonly associated with compile-time polymorphism?

A. Method overloading
B. Garbage collection
C. Exception handling
D. Namespaces

3. Which keyword enables an eligible base-class method to be overridden?

A. private
B. virtual
C. using
D. return

4. Which keyword supplies an overriding implementation in a derived class?

A. new
B. base
C. override
D. static

5. Can two methods be overloaded only by changing their return types?

A. Yes
B. No

6. What does the new keyword do when used to hide an inherited method?

A. Creates runtime overriding automatically
B. Hides the inherited member
C. Makes the method abstract
D. Converts the method to a constructor

7. Which feature lets unrelated classes expose a common capability?

A. Interfaces
B. Constructors alone
C. Private fields
D. Loops

Answers: 1-A, 2-A, 3-B, 4-C, 5-B, 6-B, 7-A.

17. Frequently Asked Questions (FAQs)

Q1. What is polymorphism in C#?

Polymorphism allows the same method name or abstraction to represent different behaviors. Method overloading and runtime method overriding are two important examples.

Q2. What is the difference between method overloading and overriding?

Overloading uses the same method name with different parameter lists. Overriding replaces an eligible inherited virtual or abstract implementation in a derived class.

Q3. What is compile-time polymorphism?

Compile-time polymorphism commonly refers to method overloading, where the compiler resolves the applicable method using the argument information and overload-resolution rules.

Q4. What is runtime polymorphism?

Runtime polymorphism allows a virtual method call to execute the override associated with the actual runtime type of the object.

Q5. Can static methods be overridden in C#?

Static methods do not participate in instance virtual dispatch and cannot be overridden using the ordinary override mechanism. Static members can be hidden with new in applicable cases.

Q6. What is the difference between new and override?

new hides an inherited member. override supplies a new implementation for an inherited virtual or abstract member and participates in runtime dispatch.

Q7. Can constructors be overloaded?

Yes. A class can have multiple constructors with different parameter lists. Constructors themselves are not overridden like virtual instance methods.

Q8. Why are interfaces useful for polymorphism?

Interfaces let different classes provide a common contract. Calling code can work with the interface without depending on the concrete implementation.

Q9. Is method overloading a type of inheritance?

No. Method overloading does not require inheritance. It is a separate mechanism that allows related methods to share a name with different parameter lists.

Q10. When should I use polymorphism?

Use polymorphism when different types need to be handled through a common abstraction while preserving their specific behavior. It is especially useful for extensible systems such as notification services, payment processors, and shape calculations.

Conclusion

Polymorphism is a core feature of C# that helps developers write flexible and extensible applications. Method overloading supports different input forms under one method name, while method overriding allows derived classes to provide specialized behavior through runtime dispatch.

By understanding virtual, override, base, method hiding, abstract classes, and interfaces, you can design applications that are easier to extend and maintain.

←
Post 16 of 30
→