
Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP). It allows objects of different types to be treated as objects of a common type. The word "polymorphism" comes from Greek, meaning "many forms." In simple terms, it enables a single method or property to behave differently based on the object it belongs to.
1. Types of Polymorphism in C#
a. Compile-Time Polymorphism (Static Polymorphism)
- What it is: Achieved using method overloading and operator overloading. The method to be executed is determined at compile time.
- Example in Real Life: A coffee machine that can make different types of coffee (espresso, latte, cappuccino) based on the button you press.
- Code Example:
class CoffeeMachine
{
    public void MakeCoffee() => Console.WriteLine("Making regular coffee.");
    public void MakeCoffee(string type) => Console.WriteLine($"Making {type} coffee.");
}
CoffeeMachine machine = new CoffeeMachine();
machine.MakeCoffee(); // Regular coffee
machine.MakeCoffee("Latte"); // Latte coffee
b. Runtime Polymorphism (Dynamic Polymorphism)
- What it is: Achieved using method overriding and interfaces. The method to be executed is determined at runtime.
- Example in Real Life: A universal remote control that works with different devices (TV, AC, Sound System). The same "power" button behaves differently depending on the device.
- Code Example:
class Device
{
    public virtual void Power() => Console.WriteLine("Device is powered on/off.");
}
class TV : Device
{
    public override void Power() => Console.WriteLine("TV is powered on/off.");
}
class AC : Device
{
    public override void Power() => Console.WriteLine("AC is powered on/off.");
}
Device myDevice = new TV();
myDevice.Power(); // TV is powered on/off.
myDevice = new AC();
myDevice.Power(); // AC is powered on/off.
2. Best Practices and Features
Best Practices
- Use Polymorphism for Extensibility: Design your code so that new types can be added without modifying existing code.
- Favor Interfaces Over Abstract Classes: Interfaces provide more flexibility for multiple inheritance.
- Avoid Overloading with Ambiguous Parameters: Ensure method overloads are clear and unambiguous.
- Use virtualandoverrideKeywords Properly: Clearly mark methods that can be overridden in derived classes.
- Follow the Liskov Substitution Principle: Ensure derived classes can substitute their base classes without breaking functionality.
Features
- Code Reusability: Write generic code that works with multiple types.
- Flexibility: Easily extend functionality by adding new classes.
- Maintainability: Changes in one part of the code don’t affect other parts.
3. Pros and Cons
Pros
- Extensibility: New classes can be added without changing existing code.
- Code Reusability: Reduces redundancy by sharing common behavior.
- Readability: Makes code more intuitive and easier to understand.
- Flexibility: Supports dynamic behavior based on the object type.
Cons
- Complexity: Can make the code harder to understand for beginners.
- Performance Overhead: Runtime polymorphism (e.g., method overriding) can introduce slight performance overhead.
- Overuse: Excessive use of polymorphism can lead to overly complex designs.
4. Alternatives
- Conditional Logic: Use if-elseorswitchstatements to handle different types. However, this can lead to messy and hard-to-maintain code.
- Pattern Matching: In modern C#, use pattern matching to handle different types in a clean way.
switch (myDevice)
{
    case TV tv: tv.Power(); break;
    case AC ac: ac.Power(); break;
    default: Console.WriteLine("Unknown device."); break;
}
5. When to Use Polymorphism
- When You Have Multiple Related Types: Use polymorphism to handle objects of different types in a unified way.
- When You Need Extensibility: Use it when you expect to add new types in the future.
- When You Want to Reduce Code Duplication: Use it to share common behavior across classes.
- When You Need Dynamic Behavior: Use runtime polymorphism to decide behavior at runtime.
Advanced Code Example
using System;
// Base class
abstract class Shape
{
    public abstract double Area(); // Abstract method
}
// Derived classes
class Circle : Shape
{
    public double Radius { get; set; }
    public Circle(double radius) => Radius = radius;
    public override double Area() => Math.PI * Radius * Radius;
}
class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }
    public override double Area() => Width * Height;
}
class Program
{
    static void Main()
    {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);
        Console.WriteLine($"Area of Circle: {circle.Area()}"); // 78.54
        Console.WriteLine($"Area of Rectangle: {rectangle.Area()}"); // 24
    }
}
Explanation of Advanced Code
- Abstract Class: Shapeis an abstract class with an abstract methodArea(). This enforces all derived classes to implement theArea()method.
- Polymorphism: The Area()method behaves differently forCircleandRectangleobjects.
- Extensibility: You can easily add new shapes (e.g., Triangle) without modifying existing code.









