1. Introduction
Imagine we've got an app for keeping track of animals in a zoo (yep, the same example we've been building on in previous lectures). We made a base class Animal with a MakeSound method so every animal can "make" some kind of sound.
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a sound.");
}
}
But then it turns out that lions and parrots sound totally different. The generic "Animal makes a sound" just doesn't cut it. That's where method overriding comes in. You want every subclass to have its own voice!
2. Syntax: how override works
Main rules
- To override a method, it has to be marked as virtual (or abstract) in the base class.
- In the derived class, use the override keyword before the method with the same signature.
Example: lions and parrots
public class Lion : Animal
{
public override void MakeSound()
{
Console.WriteLine("Rrrrrr!");
}
}
public class Parrot : Animal
{
public override void MakeSound()
{
Console.WriteLine("Polly is silly!");
}
}
Now, if you make a list of animals and call MakeSound for each one, they'll all "talk" in their own way:
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Animal()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
// Output:
// Rrrrrr!
// Polly is silly!
// Animal makes a sound.
3. Virtual method table (v-table)
When you use virtual and override, the compiler generates a special "virtual method table" (v-table) for the class.
When you call a method through a base class reference, the CLR checks the table: was this method overridden in a subclass? If yes — it calls the subclass version.
That's the "magic" of late binding, or dynamic polymorphism.
4. Putting it all together: let's keep building our app
Let's add another class:
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Woof-woof!");
}
}
And use it in our little animal tracking program:
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Dog()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
Now your code is way more flexible and maintainable, and adding new animals is a breeze!
5. Overriding properties and override with return values
Methods aren't the only thing you can override. You can also override virtual properties and indexers.
public class Animal
{
public virtual string Name { get; set; } = "Animal";
}
public class Lion : Animal
{
public override string Name { get; set; } = "Lion";
}
This is super handy when you need to specify info for a particular species.
6. Base implementation with base
Sometimes you want to extend the base method's behavior, not totally replace it.
To do that, call the base method implementation using the base keyword inside your override.
public class Parrot : Animal
{
public override void MakeSound()
{
base.MakeSound(); // "Animal makes a sound."
Console.WriteLine("Polly is silly!");
}
}
In this case, the parrot will first make the generic "zoo" sound, then bust out its signature "Polly is silly!".
7. Real-world benefits: where this is useful
Understanding how override works is a must in pretty much any serious C# project that uses OOP a lot.
- Animal models in our zoo? Already did that.
- Making custom UI controls: You override standard visual methods and add your own logic.
- Flexible business logic: Lets you build a "skeleton" of behavior in base classes, and fill in the details in subclasses.
- Testing and mocks: Easily "swap out" methods via subclasses for unit tests.
- Plugins and extensions: Interface or abstract base class, tons of implementations — and it all works thanks to proper overriding.
8. sealed override and virtual methods in chains
If you don't want anyone further down the inheritance chain to override your overridden method, you can use sealed override:
public class Base
{
public virtual void Foo() { }
}
public class Middle : Base
{
public sealed override void Foo() { }
}
public class Last : Middle
{
public override void Foo() {} // Error — can't override!
}
This lets you "close off" the override chain where it's critical for your system to work right.
9. New method (the new keyword)
Sometimes you want to declare a method with the same signature in a subclass, but the base one isn't virtual.
In that case, you can use the new keyword — but that's not polymorphism, it's more like "hiding":
public class Animal
{
public void MakeSound()
{
Console.WriteLine("I'm an animal!");
}
}
public class Cat : Animal
{
public new void MakeSound()
{
Console.WriteLine("I'm a kitty!");
}
}
Animal animal = new Cat();
animal.MakeSound(); // "I'm an animal!"
Cat cat = new Cat();
cat.MakeSound(); // "I'm a kitty!"
Here, the variable type at the moment of the call decides everything! So for real dynamic polymorphism, always use the virtual + override combo.
10. Common mistakes when overriding methods
Mistake #1: trying to override a method that wasn't declared as virtual, abstract, or override.
In C#, you can't override regular methods. If a method in the base class isn't marked with a special modifier (virtual, abstract, or override), trying to write override in the derived class will cause a compile error.
Mistake #2: method signature mismatch.
For a method to be truly overridden, its name, return type, and parameters must match the base class method exactly. Even a tiny difference (like a different parameter type) will create a new method, not an override.
Mistake #3: forgot the override modifier.
If you define a method with the same signature as in the base class but don't put override, the compiler doesn't treat it as an override. That's called method hiding (we'll cover that separately). In that case, calling the method through a base type variable will give you a surprise — the base method gets called, not yours.
GO TO FULL VERSION