Virtual & Override Keywords
Part of the Object Oriented Programming section of Coddy's C# journey — lesson 21 of 70.
When a derived class inherits a method, sometimes you want to completely change how that method behaves. The virtual and override keywords work together to make this possible in a controlled way.
Mark a method as virtual in the base class to signal that derived classes are allowed to replace its implementation:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}The override keyword in the derived class tells the compiler you're intentionally replacing the base method. Without virtual on the base method, you cannot use override.
The real power shows when you use a base class reference to hold a derived object:
Animal myPet = new Dog();
myPet.Speak(); // Output: Woof!Even though myPet is declared as Animal, C# calls the Dog version of Speak(). This is called runtime polymorphism - the actual method called depends on the object's true type, not the variable's declared type.
You can still access the base implementation using base.Speak() inside your override if you want to extend rather than completely replace the behavior.
Challenge
EasyLet's build a notification system that demonstrates how derived classes can completely override base class behavior using virtual and override keywords, while also showing the power of runtime polymorphism.
You'll create three files to organize your code:
Notification.cs: Define aNotificationclass in theAlertsnamespace. This base class represents a generic notification with aMessageproperty (string) and a constructor that sets it. Include avirtualmethod calledSend()that returns"Sending notification: {Message}".EmailNotification.cs: Define anEmailNotificationclass in theAlertsnamespace that inherits fromNotification. Add aRecipientproperty (string) and a constructor that accepts both message and recipient (usebaseto pass the message). Override theSend()method to return"Emailing {Recipient}: {Message}".SmsNotification.cs: Define anSmsNotificationclass in theAlertsnamespace that also inherits fromNotification. Add aPhoneNumberproperty (string) and a constructor that accepts both message and phone number. OverrideSend()to return"SMS to {PhoneNumber}: {Message}".Program.cs: In your main file, create instances of all three notification types using input values. Then demonstrate runtime polymorphism by storing all three inNotificationvariables and callingSend()on each one.
You will receive four inputs:
- The notification message
- Email recipient address
- Phone number for SMS
Print the result of calling Send() on each notification, stored as base class references:
{base notification Send() result}
{email notification Send() result}
{sms notification Send() result}For example, if the inputs are Meeting at 3pm, alice@email.com, and 555-1234, the output should be:
Sending notification: Meeting at 3pm
Emailing alice@email.com: Meeting at 3pm
SMS to 555-1234: Meeting at 3pmNotice how even though all three are stored as Notification variables, each one calls its own overridden version of Send(). That's runtime polymorphism in action!
Cheat sheet
Use the virtual keyword in a base class to mark a method that derived classes can override:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}Use the override keyword in a derived class to replace the base class method implementation:
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}Runtime polymorphism occurs when a base class reference holds a derived object - the actual method called depends on the object's true type, not the variable's declared type:
Animal myPet = new Dog();
myPet.Speak(); // Output: Woof!You can access the base implementation inside an override using base:
public override void Speak()
{
base.Speak(); // Calls the base class version
Console.WriteLine("Woof!");
}Try it yourself
using System;
using Alerts;
class Program
{
public static void Main(string[] args)
{
// Read inputs
string message = Console.ReadLine();
string email = Console.ReadLine();
string phone = Console.ReadLine();
// TODO: Create instances of all three notification types
// Store them in Notification variables to demonstrate polymorphism
// TODO: Create a base Notification
Notification baseNotification;
// TODO: Create an EmailNotification (stored as Notification type)
Notification emailNotification;
// TODO: Create an SmsNotification (stored as Notification type)
Notification smsNotification;
// TODO: Call Send() on each and print the results
// The overridden methods will be called due to runtime polymorphism
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesNamespaces & DirectivesIntro to Classes & ObjectsThe 'this' KeywordMethods and ParametersFields vs PropertiesConstructorsObject InitializersRecap - Simple Calculator4Inheritance
Basic Inheritance (:) SyntaxThe 'base' KeywordVirtual & Override KeywordsSealed ClassesThe 'object' Base ClassRecap - Employee Hierarchy7Advanced Features
Operator OverloadingIndexers (this[])ToString() OverrideExtension MethodsRecap - Custom List2Properties & Static Members
Auto-Implemented PropertiesRead/Write-Only PropertiesStatic Fields & MethodsStatic ClassesExpression-Bodied Members5Polymorphism & Interfaces
Compile vs Runtime PolyInterface vs Abstract ClassMultiple InterfacesExplicit InterfacesUpcasting & DowncastingRecap - Shape Calculator