C# Polymorphism

Overview

Polymorphism is a fundamental concept in object-oriented programming that allows objects of different types to be treated as objects of a common base type. It enables "one interface, multiple implementations" and provides flexibility and extensibility to your code.

Types of Polymorphism

Compile-Time Polymorphism

Method Overloading

public class Calculator
{
    // Overload 1: Two integers
    public int Add(int a, int b)
    {
        return a + b;
    }
    
    // Overload 2: Three integers
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
    
    // Overload 3: Two doubles
    public double Add(double a, double b)
    {
        return a + b;
    }
    
    // Overload 4: String concatenation
    public string Add(string a, string b)
    {
        return a + b;
    }
}

// Usage
Calculator calc = new Calculator();
Console.WriteLine(calc.Add(5, 3));        // 8
Console.WriteLine(calc.Add(1, 2, 3));     // 6
Console.WriteLine(calc.Add(1.5, 2.5));      // 4.0
Console.WriteLine(calc.Add("Hello", "World"));  // HelloWorld

Operator Overloading

public class Vector2D
{
    public double X { get; set; }
    public double Y { get; set; }
    
    public Vector2D(double x, double y)
    {
        X = x;
        Y = y;
    }
    
    // Overload + operator
    public static Vector2D operator +(Vector2D a, Vector2D b)
    {
        return new Vector2D(a.X + b.X, a.Y + b.Y);
    }
    
    // Overload - operator
    public static Vector2D operator -(Vector2D a, Vector2D b)
    {
        return new Vector2D(a.X - b.X, a.Y - b.Y);
    }
    
    // Overload * operator (scalar multiplication)
    public static Vector2D operator *(Vector2D v, double scalar)
    {
        return new Vector2D(v.X * scalar, v.Y * scalar);
    }
    
    // Overload == operator
    public static bool operator ==(Vector2D a, Vector2D b)
    {
        return Math.Abs(a.X - b.X) < 0.001 && Math.Abs(a.Y - b.Y) < 0.001;
    }
    
    public static bool operator !=(Vector2D a, Vector2D b)
    {
        return !(a == b);
    }
    
    public override string ToString()
    {
        return $"({X}, {Y})";
    }
    
    public override bool Equals(object obj)
    {
        if (obj is Vector2D other)
            return this == other;
        return false;
    }
    
    public override int GetHashCode()
    {
        return HashCode.Combine(X, Y);
    }
}

// Usage
Vector2D v1 = new Vector2D(3, 4);
Vector2D v2 = new Vector2D(1, 2);
Vector2D sum = v1 + v2;  // (4, 6)
Vector2D diff = v1 - v2;  // (2, 2)
Vector2D scaled = v1 * 2;  // (6, 8)
bool isEqual = v1 == v2;   // false

Runtime Polymorphism

Virtual Methods

public class Animal
{
    public string Name { get; set; }
    
    public Animal(string name)
    {
        Name = name;
    }
    
    public virtual void MakeSound()
    {
        Console.WriteLine($"{Name} makes a generic animal sound");
    }
    
    public virtual void Move()
    {
        Console.WriteLine($"{Name} moves in some way");
    }
}

public class Dog : Animal
{
    public Dog(string name) : base(name) { }
    
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} barks: Woof! Woof!");
    }
    
    public override void Move()
    {
        Console.WriteLine($"{Name} runs on four legs");
    }
}

public class Cat : Animal
{
    public Cat(string name) : base(name) { }
    
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} meows: Meow!");
    }
    
    public override void Move()
    {
        Console.WriteLine($"{Name} sneaks silently");
    }
}

public class Bird : Animal
{
    public Bird(string name) : base(name) { }
    
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} chirps: Tweet! Tweet!");
    }
    
    public override void Move()
    {
        Console.WriteLine($"{Name} flies through the air");
    }
}

Polymorphic Behavior

public class AnimalSoundDemo
{
    public static void MakeAnimalSound(Animal animal)
    {
        // Polymorphic call - actual method depends on runtime type
        animal.MakeSound();
    }
    
    public static void DemonstrateMovement(Animal animal)
    {
        // Polymorphic call
        animal.Move();
    }
}

// Usage
AnimalSoundDemo demo = new AnimalSoundDemo();

Animal[] animals = {
    new Dog("Buddy"),
    new Cat("Whiskers"),
    new Bird("Tweety")
};

foreach (Animal animal in animals)
{
    demo.MakeAnimalSound(animal);
    demo.DemonstrateMovement(animal);
    Console.WriteLine();
}

Abstract Classes and Polymorphism

Abstract Base Class

public abstract class Shape
{
    public string Name { get; set; }
    public double X { get; set; }
    public double Y { get; set; }
    
    protected Shape(string name, double x, double y)
    {
        Name = name;
        X = x;
        Y = y;
    }
    
    // Abstract method - must be implemented
    public abstract double CalculateArea();
    
    // Virtual method - can be overridden
    public virtual void DisplayInfo()
    {
        Console.WriteLine($"{Name} at ({X}, {Y}) - Area: {CalculateArea():F2}");
    }
    
    // Concrete method
    public void Move(double deltaX, double deltaY)
    {
        X += deltaX;
        Y += deltaY;
        Console.WriteLine($"{Name} moved to ({X}, {Y})");
    }
}

Concrete Implementations

public class Circle : Shape
{
    public double Radius { get; set; }
    
    public Circle(double x, double y, double radius) : base("Circle", x, y)
    {
        Radius = radius;
    }
    
    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
    
    public override void DisplayInfo()
    {
        Console.WriteLine($"Circle with radius {Radius}");
        base.DisplayInfo();
    }
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    public Rectangle(double x, double y, double width, double height) 
        : base("Rectangle", x, y)
    {
        Width = width;
        Height = height;
    }
    
    public override double CalculateArea()
    {
        return Width * Height;
    }
    
    public override void DisplayInfo()
    {
        Console.WriteLine($"Rectangle {Width}x{Height}");
        base.DisplayInfo();
    }
}

Polymorphic Processing

public class ShapeProcessor
{
    public static void ProcessShapes(List<Shape> shapes)
    {
        foreach (Shape shape in shapes)
        {
            // Polymorphic call - actual method depends on runtime type
            shape.DisplayInfo();
            
            // Polymorphic area calculation
            double area = shape.CalculateArea();
            Console.WriteLine($"Area: {area:F2}");
            
            // Polymorphic movement
            shape.Move(10, 5);
            
            Console.WriteLine();
        }
    }
    
    public static double CalculateTotalArea(List<Shape> shapes)
    {
        double totalArea = 0;
        foreach (Shape shape in shapes)
        {
            // Polymorphic call
            totalArea += shape.CalculateArea();
        }
        return totalArea;
    }
}

// Usage
List<Shape> shapes = new List<Shape>
{
    new Circle(0, 0, 5),
    new Rectangle(10, 10, 8, 6),
    new Circle(20, 20, 3)
};

ShapeProcessor processor = new ShapeProcessor();
processor.ProcessShapes(shapes);

double totalArea = ShapeProcessor.CalculateTotalArea(shapes);
Console.WriteLine($"Total area: {totalArea:F2}");

Interfaces and Polymorphism

Interface Definition

public interface IDrawable
{
    void Draw();
    double GetArea();
}

public interface IMovable
{
    void Move(double deltaX, double deltaY);
}

public interface IResizable
{
    void Resize(double factor);
}

Multiple Interface Implementation

public class AdvancedShape : Shape, IDrawable, IMovable, IResizable
{
    public AdvancedShape(string name, double x, double y) : base(name, x, y)
    {
    }
    
    // Implement IDrawable
    public void Draw()
    {
        Console.WriteLine($"Drawing {Name} at ({X}, {Y})");
    }
    
    public double GetArea()
    {
        return CalculateArea();
    }
    
    // Implement IMovable
    public new void Move(double deltaX, double deltaY)
    {
        base.Move(deltaX, deltaY);
        Console.WriteLine($"{Name} moved to new position");
    }
    
    // Implement IResizable
    public void Resize(double factor)
    {
        // This would need to be implemented based on shape type
        Console.WriteLine($"Resizing {Name} by factor {factor}");
    }
}

Interface-Based Polymorphism

public class DrawingManager
{
    public static void DrawAllShapes(List<IDrawable> shapes)
    {
        foreach (IDrawable shape in shapes)
        {
            shape.Draw();
            double area = shape.GetArea();
            Console.WriteLine($"Area: {area:F2}");
            Console.WriteLine();
        }
    }
    
    public static void MoveAllShapes(List<IMovable> shapes, double deltaX, double deltaY)
    {
        foreach (IMovable shape in shapes)
        {
            shape.Move(deltaX, deltaY);
        }
    }
    
    public static void ResizeAllShapes(List<IResizable> shapes, double factor)
    {
        foreach (IResizable shape in shapes)
        {
            shape.Resize(factor);
        }
    }
}

Advanced Polymorphism

Dynamic Polymorphism with Dynamic Keyword

public class DynamicProcessor
{
    public static void ProcessObject(dynamic obj)
    {
        try
        {
            // Dynamic dispatch - resolved at runtime
            Console.WriteLine(obj.ToString());
            
            // Try to call methods dynamically
            if (obj.GetType().GetMethod("GetInfo") != null)
            {
                string info = obj.GetInfo();
                Console.WriteLine($"Info: {info}");
            }
        }
        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
        {
            Console.WriteLine($"Method not found: {ex.Message}");
        }
    }
}

Generic Polymorphism

public class GenericProcessor<T> where T : class
{
    public void ProcessItem(T item)
    {
        Console.WriteLine($"Processing {typeof(T).Name}");
        
        // Use reflection for polymorphic behavior
        var methods = typeof(T).GetMethods();
        
        foreach (var method in methods)
        {
            if (method.Name == "ToString")
            {
                var result = method.Invoke(item, null);
                Console.WriteLine($"ToString result: {result}");
            }
        }
    }
}

public class PolymorphicContainer<T>
{
    private List<T> _items = new List<T>();
    
    public void Add(T item)
    {
        _items.Add(item);
    }
    
    public void ProcessAll()
    {
        GenericProcessor<T> processor = new GenericProcessor<T>();
        foreach (T item in _items)
        {
            processor.ProcessItem(item);
        }
    }
}

Covariance and Contravariance

// Covariance: Generic type parameter can be more derived
public interface ICovariant<out T>
{
    T GetItem();
}

public class CovariantExample : ICovariant<string>
{
    public string GetItem() => "Hello";
}

// Contravariance: Generic type parameter can be less derived
public interface IContravariant<in T>
{
    void ProcessItem(T item);
}

public class ContravariantExample : IContravariant<object>
{
    public void ProcessItem(object item)
    {
        Console.WriteLine($"Processing: {item}");
    }
}

Practical Examples

Payment Processing System

public abstract class PaymentMethod
{
    public string Name { get; set; }
    public decimal Amount { get; set; }
    
    protected PaymentMethod(string name, decimal amount)
    {
        Name = name;
        Amount = amount;
    }
    
    public abstract bool ProcessPayment();
    public abstract void GenerateReceipt();
}

public class CreditCardPayment : PaymentMethod
{
    public string CardNumber { get; set; }
    public string ExpiryDate { get; set; }
    
    public CreditCardPayment(string cardNumber, string expiryDate, decimal amount) 
        : base("Credit Card", amount)
    {
        CardNumber = cardNumber;
        ExpiryDate = expiryDate;
    }
    
    public override bool ProcessPayment()
    {
        Console.WriteLine($"Processing credit card payment of ${Amount}");
        Console.WriteLine($"Card: ****-****-****-{CardNumber.Substring(CardNumber.Length - 4)}");
        
        // Simulate payment processing
        return true;
    }
    
    public override void GenerateReceipt()
    {
        Console.WriteLine($"=== {Name} Receipt ===");
        Console.WriteLine($"Amount: ${Amount:N2}");
        Console.WriteLine($"Card: ****-****-****-{CardNumber.Substring(CardNumber.Length - 4)}");
        Console.WriteLine($"Date: {DateTime.Now:yyyy-MM-dd HH:mm}");
    }
}

public class PayPalPayment : PaymentMethod
{
    public string Email { get; set; }
    public string Password { get; set; }
    
    public PayPalPayment(string email, string password, decimal amount) 
        : base("PayPal", amount)
    {
        Email = email;
        Password = password;
    }
    
    public override bool ProcessPayment()
    {
        Console.WriteLine($"Processing PayPal payment of ${Amount}");
        Console.WriteLine($"Account: {Email}");
        
        // Simulate payment processing
        return true;
    }
    
    public override void GenerateReceipt()
    {
        Console.WriteLine($"=== {Name} Receipt ===");
        Console.WriteLine($"Amount: ${Amount:N2}");
        Console.WriteLine($"Account: {Email}");
        Console.WriteLine($"Date: {DateTime.Now:yyyy-MM-dd HH:mm}");
    }
}

public class PaymentProcessor
{
    public void ProcessPayment(PaymentMethod payment)
    {
        Console.WriteLine($"Processing payment with {payment.Name}");
        
        // Polymorphic call
        bool success = payment.ProcessPayment();
        
        if (success)
        {
            payment.GenerateReceipt();
        }
        else
        {
            Console.WriteLine("Payment failed!");
        }
    }
}

Media Player System

public abstract class MediaPlayer
{
    public string Title { get; set; }
    public string Artist { get; set; }
    
    protected MediaPlayer(string title, string artist)
    {
        Title = title;
        Artist = artist;
    }
    
    public abstract void Play();
    public abstract void Pause();
    public abstract void Stop();
    public abstract void SetVolume(int volume);
}

public class MP3Player : MediaPlayer
{
    private int _volume = 50;
    
    public MP3Player(string title, string artist) : base(title, artist) { }
    
    public override void Play()
    {
        Console.WriteLine($"Playing MP3: {Title} by {Artist}");
    }
    
    public override void Pause()
    {
        Console.WriteLine("MP3 paused");
    }
    
    public override void Stop()
    {
        Console.WriteLine("MP3 stopped");
    }
    
    public override void SetVolume(int volume)
    {
        _volume = Math.Max(0, Math.Min(100, volume));
        Console.WriteLine($"MP3 volume set to {_volume}");
    }
}

public class VideoPlayer : MediaPlayer
{
    private int _volume = 50;
    
    public VideoPlayer(string title, string artist) : base(title, artist) { }
    
    public override void Play()
    {
        Console.WriteLine($"Playing Video: {Title} by {Artist}");
    }
    
    public override void Pause()
    {
        Console.WriteLine("Video paused");
    }
    
    public override void Stop()
    {
        Console.WriteLine("Video stopped");
    }
    
    public override void SetVolume(int volume)
    {
        _volume = Math.Max(0, Math.Min(100, volume));
        Console.WriteLine($"Video volume set to {_volume}");
    }
}

public class MediaController
{
    public void ControlMedia(MediaPlayer player)
    {
        // Polymorphic control
        player.Play();
        System.Threading.Thread.Sleep(2000);
        player.Pause();
        System.Threading.Thread.Sleep(1000);
        player.Play();
        player.SetVolume(75);
        System.Threading.Thread.Sleep(2000);
        player.Stop();
    }
}

Best Practices

Polymorphism Design Guidelines

// Good: Program to interfaces, not implementations
public void DrawShapes(List<IDrawable> shapes)
{
    foreach (IDrawable shape in shapes)
    {
        shape.Draw();
    }
}

// Good: Use abstract classes for shared implementation
public abstract class Animal
{
    protected string Name { get; set; }
    
    public Animal(string name) { Name = name; }
    
    public virtual void MakeSound() { /* Default implementation */ }
    public abstract void Move();  // Must be implemented
}

// Good: Follow Liskov Substitution Principle
public class Derived : Base
{
    public override void SomeMethod()
    {
        // Can substitute for Base without breaking functionality
        base.SomeMethod();
    }
}

When to Use Each Type

// Use compile-time polymorphism when:
// - You know the exact types at compile time
// - You need method overloading for different parameter types
public class MathUtils
{
    public int Add(int a, int b) => a + b;
    public double Add(double a, double b) => a + b;
    public string Add(string a, string b) => a + b;
}

// Use runtime polymorphism when:
// - You need to work with different types through a common interface
// - You want extensibility through inheritance
public void ProcessAnimals(List<Animal> animals)
{
    foreach (Animal animal in animals)
    {
        animal.MakeSound();  // Polymorphic call
    }
}

Summary

In this chapter, you learned:

  • Types of polymorphism: compile-time and runtime
  • Method overloading and operator overloading
  • Virtual methods and method overriding
  • Abstract classes and their role in polymorphism
  • Interface-based polymorphism
  • Advanced concepts: dynamic dispatch, generics, variance
  • Practical examples and best practices

Polymorphism is a powerful tool that enables flexible, extensible, and maintainable code. It allows you to write code that can work with different types through a common interface, making your applications more adaptable to changing requirements. In the next chapter, we'll explore interfaces in detail to define contracts and achieve multiple inheritance.