Chapter 13: Polymorphism
1. What is Polymorphism? (Super Simple Analogy)
Imagine you have a remote control with a big “Play” button:
- When you press “Play” on a Music Player โ it plays songs ๐ต
- When you press “Play” on a DVD Player โ it plays movies ๐ฌ
- When you press “Play” on a Game Console โ it starts the game ๐ฎ
The same button (“Play”) does different things depending on what device itโs connected to. Thatโs polymorphism in real life!
In C#:
- You define a common interface or base class with a method (like Play())
- Different derived classes provide their own version of that method
- You can call the method the same way on all objects, but each one behaves differently
2. Method Overriding โ The Foundation of Polymorphism
We already saw this in the Inheritance chapter, but now weโll use it for true polymorphic behavior.
Key points:
- Base class marks method as virtual
- Derived class provides new implementation with override
- You can call the method through a base class reference โ it runs the derived class version
Example โ Animals Making Sounds
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
class Animal { public string Name { get; set; } // virtual = "you can override me" public virtual void MakeSound() { Console.WriteLine($"{Name} makes a generic animal sound..."); } } class Dog : Animal { public Dog(string name) => Name = name; // override = "I provide my own version" public override void MakeSound() { Console.WriteLine($"{Name} says: Woof woof! ๐ถ"); } } class Cat : Animal { public Cat(string name) => Name = name; public override void MakeSound() { Console.WriteLine($"{Name} says: Meow meow! ๐ฑ"); } } class Lion : Animal { public Lion(string name) => Name = name; public override void MakeSound() { Console.WriteLine($"{Name} roars: ROARRRRR! ๐ฆ"); } } |
Polymorphic usage โ treating all as Animal
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Animal[] zoo = new Animal[] { new Dog("Buddy"), new Cat("Whiskers"), new Lion("Simba"), new Animal { Name = "Unknown" } }; Console.WriteLine("=== Zoo Sounds ===\n"); foreach (Animal animal in zoo) { animal.MakeSound(); // Calls the correct overridden version! } |
Output:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
=== Zoo Sounds === Buddy says: Woof woof! ๐ถ Whiskers says: Meow meow! ๐ฑ Simba roars: ROARRRRR! ๐ฆ Unknown makes a generic animal sound... |
Magic moment: Even though the array is of type Animal[], each call to MakeSound() runs the correct derived class version โ thatโs polymorphism!
3. Abstract Classes & Abstract Methods โ Forcing Implementation
Sometimes you want a base class to be a template that cannot be used directly, and you force every child to provide its own implementation.
Abstract class โ cannot be instantiated (new Animal() would fail) Abstract method โ has no body in base class โ must be overridden in every derived class
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
abstract class Shape { public string Color { get; set; } = "White"; // Abstract method โ no body, must be overridden public abstract double CalculateArea(); // Concrete method โ children inherit it as-is public void DisplayColor() { Console.WriteLine($"This shape is {Color}"); } } class Circle : Shape { public double Radius { get; set; } public Circle(double radius, string color) { Radius = radius; Color = color; } // Must override abstract method public override double CalculateArea() { return Math.PI * Radius * Radius; } } class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } public Rectangle(double width, double height, string color) { Width = width; Height = height; Color = color; } public override double CalculateArea() { return Width * Height; } } |
Polymorphic usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Shape[] shapes = new Shape[] { new Circle(5, "Red"), new Rectangle(4, 6, "Blue") }; foreach (Shape shape in shapes) { shape.DisplayColor(); Console.WriteLine($"Area: {shape.CalculateArea():F2} square units\n"); } |
Output:
|
0 1 2 3 4 5 6 7 8 9 10 |
This shape is Red Area: 78.54 square units This shape is Blue Area: 24.00 square units |
Key rule: Every non-abstract derived class must implement all abstract methods from the base class.
4. Polymorphic Behavior in Real Projects
You can store different types in the same collection and treat them uniformly.
Example โ Payment Processor
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
abstract class Payment { public decimal Amount { get; set; } public abstract void ProcessPayment(); } class CreditCardPayment : Payment { public string CardNumber { get; set; } public override void ProcessPayment() { Console.WriteLine($"Processing โน{Amount} via Credit Card ending in {CardNumber[^4..]}"); } } class UPIPayment : Payment { public string UPIId { get; set; } public override void ProcessPayment() { Console.WriteLine($"Processing โน{Amount} via UPI to {UPIId}"); } } class CashPayment : Payment { public override void ProcessPayment() { Console.WriteLine($"Received โน{Amount} in cash. Thank you! ๐ต"); } } // Usage Payment[] payments = new Payment[] { new CreditCardPayment { Amount = 1500, CardNumber = "1234567890123456" }, new UPIPayment { Amount = 499, UPIId = "webliance@upi" }, new CashPayment { Amount = 200 } }; foreach (Payment p in payments) { p.ProcessPayment(); } |
Mini-Project: Vehicle Rental System with Polymorphism
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
abstract class Vehicle { public string Brand { get; set; } public decimal DailyRate { get; set; } public abstract string GetDescription(); public virtual decimal CalculateRentalCost(int days) { return DailyRate * days; } } class Car : Vehicle { public int Seats { get; set; } public override string GetDescription() { return $"{Brand} Car - {Seats} seats"; } public override decimal CalculateRentalCost(int days) { decimal baseCost = base.CalculateRentalCost(days); return days >= 7 ? baseCost * 0.9m : baseCost; // 10% discount for week+ } } class Bike : Vehicle { public bool HasHelmet { get; set; } public override string GetDescription() { return $"{Brand} Bike {(HasHelmet ? "with helmet" : "no helmet")}"; } } // Usage Vehicle[] rentals = new Vehicle[] { new Car { Brand = "Toyota", DailyRate = 2500, Seats = 5 }, new Bike { Brand = "Honda", DailyRate = 500, HasHelmet = true } }; foreach (Vehicle v in rentals) { Console.WriteLine(v.GetDescription()); Console.WriteLine($"Rental for 5 days: โน{v.CalculateRentalCost(5):N2}\n"); } |
Summary โ What We Learned Today
- Polymorphism = “many forms” โ same method call, different behavior
- virtual + override โ classic overriding
- Abstract classes & methods โ force derived classes to implement
- Polymorphic collections โ store different types in base type array/list
- Real power โ write code that works with any derived class without knowing the exact type
Your Homework (Super Practical!)
- Create a new console project called PolymorphismMaster
- Create an abstract class Employee with:
- Properties: Name, BaseSalary
- Abstract method: CalculateBonus()
- Concrete method: GetFullInfo()
- Create at least 3 derived classes:
- Developer โ bonus = 10% of salary + โน500 per project
- Manager โ bonus = 15% + โน2000 per team member
- Intern โ bonus = fixed โน2000
- In Program.cs: Create a list of employees (mixed types), loop through them, and print name + total salary (base + bonus)
Next lesson: Interfaces โ weโre going to learn how to make classes even more flexible without inheritance!
Youโre doing absolutely fantastic! ๐ Any part confusing? Want more examples with abstract classes or polymorphism? Just tell me โ Iโm right here for you! ๐
