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")); // HelloWorldOperator 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; // falseRuntime 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.