Skip to content

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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
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

csharp
// 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

csharp
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

csharp
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

csharp
// 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

csharp
// 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.

Content is for learning and research only.