virtual, override and new
virtual offers a method up for replacement, override replaces it, and new only hides it, which is rarely what anyone wanted.
public virtual string Format(string m) { } / public override string Format(string m) { }A derived class often needs a method to behave differently from the base class version. virtual and override are how that is done. The base class marks a method virtual, meaning "a derived class may replace this". The derived class marks its own version override, meaning "this replaces the base version".
The payoff is that calling code does not need to know which kind of object it holds. It calls the method and the object supplies the behaviour.
Calling the version you replaced
Inside an override, base.Method() calls the base class version. That lets you add to the old behaviour instead of discarding it. Without base. the call would find your own override and recurse forever.
What override requires
You can only override a member marked virtual, abstract or override. The signature must match: same name, same parameter types, same accessibility. The return type must match too, with one exception that has applied since C# 9: an override, or a read-only property override, may return a type derived from the base member's return type, which is called a covariant return type. Properties and indexers can be virtual too, not just methods. sealed override replaces the member and forbids any further class from replacing it again.
new hides, and that is different
If the base member is not virtual, you can still declare a member with the same name and mark it new. That hides the base member rather than replacing it. Which one runs is decided by the declared type of the variable, not by the object, so the same object answers differently through two variables. Omitting new in that situation produces a compiler warning, and writing new and override on the same member is an error.
Example
Notifier plain = new Notifier();
Notifier loud = new LoudNotifier();
Console.WriteLine(plain.Format("deploy finished"));
Console.WriteLine(loud.Format("deploy finished"));
class Notifier
{
// virtual: a derived class is allowed to replace this
public virtual string Format(string message)
{
return $"[info] {message}";
}
}
class LoudNotifier : Notifier
{
// override: this replaces Notifier.Format for LoudNotifier objects
public override string Format(string message)
{
// base.Format calls the version in Notifier, not this one
string original = base.Format(message);
return original.ToUpperInvariant();
}
}
// Output:
// [info] deploy finished
// [INFO] DEPLOY FINISHEDWhen to use it
- Give a base report class a default Render and let the PDF subclass change only the header.
- Override a retry count on one job type while every other job keeps the default.
- Wrap a logging method so the derived version adds a prefix and then calls base.
More examples
new hides, so the declared type decides
This is the behaviour that makes new a mistake in almost every case. Both variables point at the same FancyButton, yet the answer depends on how the variable was declared. The legitimate use is narrow: a base class you do not control added a member whose name you already use, and new silences the warning without changing anything.
Button asButton = new FancyButton();
FancyButton asFancy = new FancyButton();
// One object, two variables, two different answers
Console.WriteLine(asButton.Label());
Console.WriteLine(asFancy.Label());
class Button
{
// not virtual, so FancyButton cannot override it
public string Label() => "plain";
}
class FancyButton : Button
{
// new hides Button.Label; it does not replace it
public new string Label() => "fancy";
}
// Output:
// plain
// fancyVirtual property, and sealed override to stop the chain
Use a virtual property when the derived class should supply a value rather than run an action. Use sealed override when a method has become load-bearing for correctness and further replacement would break an assumption, for example a method that has to produce a stable format for an audit log.
class Job
{
// properties can be virtual too
public virtual int Retries => 3;
public virtual string Render() => "job";
}
class PaymentJob : Job
{
public override int Retries => 10;
// sealed override: replaces Render, and no class below may replace it again
public sealed override string Render() => "payment job";
}
class CardPaymentJob : PaymentJob
{
public override int Retries => 1;
// public override string Render() => "card"; // error: Render is sealed
}The virtual call from a constructor trap
The base constructor finishes before the derived constructor body starts, but the virtual call already dispatches to the derived override. Reach for this example when a field is mysteriously null inside a method that is only ever called after construction. The fix is to move the call out of the constructor into an explicit initialisation step.
new Derived();
class Base
{
public Base()
{
// Runs the override in Derived, before Derived's constructor body
Console.WriteLine(Describe());
}
public virtual string Describe() => "base";
}
class Derived : Base
{
private readonly string? _name;
public Derived()
{
_name = "ready";
}
public override string Describe() => $"name is {_name ?? "not set yet"}";
}
// Output:
// name is not set yet
Discussion