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.
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.
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.
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 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:
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.
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.
}
}
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.
}
}
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.
}
}
In this example:
Payment
that defines a general MakePayment
method.CreditCardPayment
and PayPalPayment
, which override the base method to provide their specific implementations for processing payments.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.
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#.