If you’ve been developing with C# for a while, chances are you’ve hesitated at least once between making a method abstract or virtual.
Both support polymorphism. Both are overridden in derived classes. Both look similar at first glance.
Let's break down what each keyword really means, why developers confuse them, and how to choose the right one with confidence.
What Does Abstract Really Mean?
An abstract member says:
“This behavior must exist, but I refuse to define it here.”
An abstract method, has no implementation, must be overridden in derived classes, exists only inside an abstract class
public abstract class PaymentProcessor
{
public abstract void Process(decimal amount);
}
Any class inheriting from this must implement Process:
public class CreditCardPayment : PaymentProcessor
{
public override void Process(decimal amount)
{
Console.WriteLine($"Processing credit card payment: {amount}");
}
}
If you forget to override it, the compiler stops you.
When to Use abstract
Use abstract classes when:
What Does virtual Really Mean?
A virtual member says:
“Here’s the default behavior, override it if you need to.”
A virtual method, has a default implementation, may or may not be overridden, allows incremental customization.
public class Logger
{
public virtual void Log(string message)
{
Console.WriteLine(message);
}
}
Derived classes can override it, but they’re not forced to:
public class FileLogger : Logger
{
public override void Log(string message)
{
File.AppendAllText("log.txt", message);
}
}
Or they can inherit the default behavior unchanged.
Why Developers Choose the Wrong One
1. “I Might Override This Later”
Developers often use virtual just in case, even when overriding should be mandatory.
This leads to:
If a method must be implemented correctly to function, it should be abstract.
2. Misunderstanding Flexibility
Some believe virtual is “more flexible” than abstract.
In reality:
Flexibility without constraints often leads to fragile systems.
Abstract + Virtual Together?
An abstract class can mix both:
public abstract class Order
{
public abstract decimal CalculateTotal();
public virtual bool IsValid()
{
return true;
}
}
In domain-driven design:
=> A domain entity with optional overrides is usually a design smell.
=> A framework base class with abstract methods often forces too much rigidity.