C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
166: The virtual and override Modifiers
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);
}
}There are no comments for now.