Skip to Content
Course content

20: Inheritance and Polymorphism

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a small billing module the other day and realized I was writing the same three properties—Amount, TransactionDate, and PaymentId—over and over again for every single payment method. It felt wrong. When you find yourself copy-pasting code across three different classes, that's usually a signal that your architecture is missing a layer.

Stopping the repetition

Initially, I had separate classes for CreditCardPayment and PaypalPayment. Both had the exact same basic data. To fix this, I decided to pull those common pieces of data up into a "parent" class. I'll call it Payment.

public class Payment 
{
    public decimal Amount { get; set; }
    public DateTime TransactionDate { get; set; }
    public string PaymentId { get; set; }

    public void Process() 
    {
        Console.WriteLine($"Processing generic payment of {Amount}...");
    }
}

public class CreditCardPayment : Payment 
{
    public string CardNumber { get; set; }
}

public class PaypalPayment : Payment 
{
    public string EmailAddress { get; set; }
}

By using the : syntax, CreditCardPayment now "inherits" everything from Payment. I don't have to redeclare the amount or the date; it's just there. This is a huge win for maintenance. If I need to add a Currency property later, I add it once to the base class, and every single payment type gets it automatically.

The "Wrong Method" trap

Here is where I hit a wall. I wanted to process a list of different payments in one loop. I thought, "Since they are all Payments, I can just put them in a List<Payment>." It seemed logical. But watch what happens when I try to specialize the Process method for the credit card.

public class CreditCardPayment : Payment 
{
    public string CardNumber { get; set; }
    
    public void Process() 
    {
        Console.WriteLine($"Charging credit card {CardNumber} for {Amount}...");
    }
}

// ... in my main code ...
List<Payment> payments = new List<Payment> { 
    new CreditCardPayment { Amount = 100, CardNumber = "1234-..." }, 
    new PaypalPayment { Amount = 50, EmailAddress = "user@example.com" } 
};

foreach (var p in payments) 
{
    p.Process(); 
}

I expected to see "Charging credit card..." for the first item. Instead, the console printed "Processing generic payment..." for both. I was baffled for a second. The problem is that C# looks at the type of the reference (which is Payment), not the type of the actual object (which is CreditCardPayment), when deciding which method to call. My CreditCardPayment.Process() method wasn't replacing the base one; it was just hiding it.

Unlocking Polymorphism

To fix this, I need to tell C# that the Process method is intended to be swapped out by child classes. This is where virtual and override come in. I have to explicitly grant permission for the method to be changed.

First, I'll mark the base method as virtual. Then, in the child class, I'll use the override keyword. This is the core of polymorphism: the ability of an object to behave differently based on its actual derived type, even when treated as its base type.

public class Payment 
{
    // "virtual" means: "Children are allowed to change how this works"
    public virtual void Process() 
    {
        Console.WriteLine("Processing generic payment...");
    }
}

public class CreditCardPayment : Payment 
{
    public string CardNumber { get; set; }

    // "override" means: "I am replacing the base version with my own"
    public override void Process() 
    {
        Console.WriteLine($"Charging credit card {CardNumber} for {Amount}...");
    }
}

Now, when I run that same foreach loop, the magic happens. Even though the list is defined as List<Payment>, C# checks the actual object at runtime. It sees that the first object is a CreditCardPayment and calls the overridden version. The second object is just a Payment (or a PaypalPayment without an override), so it uses the base logic.

I personally find this incredibly powerful because it lets me write code that says "Process all these payments" without needing to know exactly what kind of payments they are. The objects themselves handle the specifics.




📋 Practical Task

Build a Versatile Game Character Ability System

You are building a combat system for a game. Instead of writing separate logic for every character type, you will use inheritance and polymorphism to handle their special attacks.

  • Create a base class called GameCharacter with a property for Name and a virtual method called PerformAttack() that prints "The character performs a basic attack!".
  • Create a derived class Warrior that overrides PerformAttack() to print "[Name] swings a massive claymore!".
  • Create a derived class Mage that overrides PerformAttack() to print "[Name] casts a fireball!".
  • In your main program, create a List<GameCharacter> containing at least one Warrior and one Mage.
  • Loop through the list and call PerformAttack() on each character to demonstrate that the correct specialized attack is triggered for each type.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.