Skip to Content
Course content

166: The virtual and override Modifiers

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

I've seen this exact bug pop up in countless code reviews. You've built a nice class hierarchy, you've written a specialized version of a method in your child class, but for some reason, when you run the code, the program acts like the child class doesn't even exist. It just keeps running the logic from the base class.

Let's look at a realistic example. Imagine we're building a simple combat system for a game. We have a base Enemy class and a specific BossEnemy that is supposed to take less damage because it has armor.

public class Enemy 
{
    public void TakeDamage(int amount) 
    {
        Console.WriteLine($"Enemy takes {amount} damage!");
    }
}

public class BossEnemy : Enemy 
{
    public void TakeDamage(int amount) 
    {
        int reducedDamage = amount / 2;
        Console.WriteLine($"Boss armor reduces damage! Boss takes {reducedDamage} damage.");
    }
}

// In the game loop:
Enemy currentEnemy = new BossEnemy();
currentEnemy.TakeDamage(20); 

The disappearing behavior

If you run this, you'll expect to see the "Boss armor" message. Instead, the console prints: Enemy takes 20 damage!

Wait, what happened? We clearly instantiated a BossEnemy. The problem is that by default, methods in C# are non-virtual. When you defined TakeDamage in BossEnemy without any special keywords, you didn't actually replace the base method; you just "hid" it.

Because the variable currentEnemy is typed as the base Enemy class, C# looks at the Enemy class and says, "Okay, I see a TakeDamage method here. I'll use that." It doesn't care that the actual object in memory is a BossEnemy because it wasn't told that TakeDamage was allowed to change based on the object's actual type.

Enabling polymorphism with virtual and override

To fix this, we need to explicitly tell the compiler that the TakeDamage method is intended to be redefined in derived classes. This is where virtual and override come in.

First, we mark the base method as virtual. This is basically a signal saying, "I'm providing a default implementation, but I'm giving permission for any child class to replace this logic."

Then, in the child class, we use the override keyword. This tells C#, "I know the base class has this method, and I am intentionally replacing it with my own version."

public class Enemy 
{
    // Added 'virtual' here
    public virtual void TakeDamage(int amount) 
    {
        Console.WriteLine($"Enemy takes {amount} damage!");
    }
}

public class BossEnemy : Enemy 
{
    // Added 'override' here
    public override void TakeDamage(int amount) 
    {
        int reducedDamage = amount / 2;
        Console.WriteLine($"Boss armor reduces damage! Boss takes {reducedDamage} damage.");
    }
}

// Now this works as expected:
Enemy currentEnemy = new BossEnemy();
currentEnemy.TakeDamage(20); // Prints: Boss armor reduces damage! Boss takes 10 damage.

Now, when C# sees the call to TakeDamage, it checks if the method is virtual. Since it is, it doesn't just look at the variable type (Enemy); it looks at the actual object type (BossEnemy) and calls the most specific version of that method available. This is the core of polymorphism.

One quick tip: if you don't want a method to be overridden at all, just leave it alone. If you want to ensure that a child class must implement its own version (without a default in the base), you'd use abstract, but we'll dive deeper into that in a later lesson. For now, just remember: virtual in the parent, override in the child.




📋 Practical Task

Fixing the Payment Processor Hierarchy

You are working on a payment system. There is a base PaymentProcessor class and a PayPalProcessor class. The PayPalProcessor is supposed to add a 3% transaction fee to every payment, but currently, the system is ignoring the fee and using the base logic.

Your Task: Modify the provided code by adding the correct virtual and override modifiers so that when a PayPalProcessor is called via a PaymentProcessor reference, the fee is correctly applied.

using System;

public class PaymentProcessor
{
    public void ProcessPayment(double amount)
    {
        Console.WriteLine($"Processing generic payment of ${amount}");
    }
}

public class PayPalProcessor : PaymentProcessor
{
    public void ProcessPayment(double amount)
    {
        double finalAmount = amount * 1.03;
        Console.WriteLine($"Processing PayPal payment of ${finalAmount} (including fee)");
    }
}

public class Program
{
    public static void Main()
    {
        // This currently prints: "Processing generic payment of $100"
        // It SHOULD print: "Processing PayPal payment of $103 (including fee)"
        PaymentProcessor processor = new PayPalProcessor();
        processor.ProcessPayment(100.00);
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.