Virtual vs Override vs Partial in C# Explained with Small Runnable Examples
Virtual, override and partial are tiny C# keywords with huge impact on design. This post explains how they work, when to use them, and where bugs appear. Learn the difference between override and new, why sealed override matters, and how partial classes and partial methods keep generated and hand written code happy. Every section comes with short, runnable snippets and real project tips.
Ever stared at a C# class and thought this method looks important but why is it virtual, and why is someone overriding it three folders away? In this post, we’ll unpack what virtual and override really do and why they matter, show when new is the wrong tool, use sealed override to freeze behavior at the right layer, and share a couple of gotchas that save you a weekend
Virtual and Override in plain English
Virtual is how a base class says I am giving you an extension point. Override is how a derived class says I am taking that extension point and giving it custom behavior. If you need polymorphism, this is your bread and butter.
Let’s sketch a tiny Office flavored example.
public class Employee{ public virtual string Pitch() => "I sell paper.";}
public class AssistantToTheRegionalManager : Employee{ public override string Pitch() => "Bears. Beets. Battlestar Galactica.";}Now we can treat a derived instance like its base and still get the custom behavior.
Employee jim = new AssistantToTheRegionalManager();Console.WriteLine(jim.Pitch()); // prints Dwight's pitchA couple of truths:
- You can only override members that are virtual, abstract, or already override
- Overriding keeps polymorphism intact through base references
Overriding properties and stopping the chain with sealed override
Methods get most of the spotlight, but properties can be virtual too. Sometimes you also need to close the door for further overrides.
public class SalesPerson{ public virtual decimal CommissionRate => 0.02m;}
public class Manager : SalesPerson{ public sealed override decimal CommissionRate => 0.05m;}Here Manager raises the rate and seals it. Any class deriving from Manager will see that rate as read only behavior.
new vs override - the plot twist you do not want
new hides a member. It does not participate in polymorphism. Most bugs I see around inheritance start here.
public class BasePrinter{ public void Hello() => Console.WriteLine("Base");}
public class FancyPrinter : BasePrinter{ public new void Hello() => Console.WriteLine("Fancy");}Now watch what happens when we upcast.
var p = new BasePrinter();BasePrinter up = new FancyPrinter();p.Hello(); // Baseup.Hello(); // Base again - because Hello is hidden, not overriddenIf you intended polymorphism, the base member must be virtual and the derived member must override it.
Calling virtual members in constructors - proceed with care
Constructors run base first, derived second. If the base constructor calls a virtual member, it will dispatch to the derived override before the derived constructor runs. That can lead to surprising state.
public class BaseRoom{ public BaseRoom() { Describe(); } public virtual void Describe() => Console.WriteLine("Base");}public class SurpriseRoom : BaseRoom{ private int traps; public SurpriseRoom(int t) { traps = t; } public override void Describe() => Console.WriteLine($"Traps: {traps}");}var room = new SurpriseRoom(42); // prints "Traps: 0" during base constructionDuring the base constructor call, traps is still at its default value. Prefer avoiding virtual calls in constructors or ensure overrides can run safely on partially constructed objects.
What about abstract
Quick pit stop. Abstract methods force derived classes to implement them and behave like required overrides. Virtual provides a default implementation that can be changed. Abstract is a contract. Virtual is an option.
Partial classes - one type, many files
Partial lets you split a type definition across files. It is great for code generation and for keeping hand written code away from generated code. You still get one compiled type.
public partial class Wizard{ public string Name { get; } public Wizard(string name) => Name = name;}public partial class Wizard{ public void Cast(string spell) => Console.WriteLine($"{Name} casts {spell}!");}var g = new Wizard("Gandalf");g.Cast("You shall not pass");You will see this pattern in WinForms, WPF, Razor, EF Core scaffolding and source generator outputs. The generated file defines the structure. Your file adds behavior without risking merge conflicts when regen happens.
Partial methods - lightweight hooks
Partial methods are tiny hook points inside partial types. Before C# 9 they had to be private and void. If you did not implement them, calls were removed at compile time. That makes them perfect for optional behavior.
public partial class Droid{ partial void OnBoot(); public void Boot() { Console.WriteLine("Booting"); OnBoot(); }}public partial class Droid{ partial void OnBoot() => Console.WriteLine("Beep boop");}Modern C# expanded partial methods so they can have accessibility and return values, but they still cannot be virtual or override. If you give a partial method accessibility or a non void return type, it must be implemented somewhere in the partial type.
public partial class Report{ public partial string Format();}public partial class Report{ public partial string Format() => "Formatted by BBB";}Where to use what
- Use virtual when you want to offer safe extension points
- Use override to specialize behavior in a derived type
- Use sealed override to stop the override chain at the right layer
- Avoid new unless you truly want to hide, not polymorph
- Use partial types to separate generated and hand written code
- Use partial methods to create optional hooks in generated models
Wrap up
Virtual, override and partial are small words that shape big designs. Pick the right extension points, avoid the new trap, seal behavior where it must not vary, and use partial to keep generated and custom code in a healthy relationship.