Last Updated: May 17, 2026
A derived class inherits everything its parent has, including method bodies. Often that's exactly what you want. Sometimes the inherited method is wrong for the derived type and needs a different implementation. Method overriding is the mechanism C# provides for replacing an inherited method body in a derived class, while keeping the same name, return type, and parameter list. The two keywords involved are virtual on the base method and override on the derived one.
A Product class in a catalog has a method that returns the price a customer pays. For a plain product, the price is whatever the catalog says.
Now the catalog introduces discounted products. A DiscountedProduct is still a product, so it should inherit from Product, but its CalculatePrice needs to subtract a discount.
The first attempt looks like this:
The code compiles, but the compiler emits a warning:
That warning is the language pointing at the real problem. The new CalculatePrice doesn't override the inherited one; it sits next to it as an unrelated method that happens to share the same name. The behavior at runtime depends on the static type of the variable holding the object, which is fragile.
What we actually want is for DiscountedProduct.CalculatePrice to replace the inherited method, so any code calling CalculatePrice on a DiscountedProduct gets the discounted version. That is what virtual and override are for.
virtualThe base class must explicitly allow a method to be overridden. In C#, methods are not overridable by default. You add the virtual keyword to a method to mark it as "derived classes may replace this."
The single word virtual changes the contract. The method still has a default implementation (return the list price), but a derived class is now free to provide a different one. If a derived class doesn't override it, the base implementation runs, as it always did.
virtual can be applied to:
It cannot be applied to:
static methods. Static members belong to the type, not an instance, so there's no instance to dispatch through.private methods. A derived class can't see them, so it can't override them.Trying to mark a static or private method as virtual is a compile error:
The keyword goes between the access modifier and the return type:
overrideOn the derived side, you replace the inherited body by declaring a method with the same signature and adding the override keyword.
The override keyword tells the compiler two things at once. First, "I know there is a virtual method in a base class with this exact signature." Second, "Use my body when this method is called on an instance of my type or a derived type."
A full program shows the result:
The plain Product runs the base method. The DiscountedProduct runs the overridden method. Same call shape (CalculatePrice()), different behavior, chosen by the actual type of the object.
override is strict. The signature in the derived class has to match the base method on every axis the compiler checks:
| Part | Must Match? |
|---|---|
| Method name | Yes |
| Parameter count | Yes |
| Parameter types (in order) | Yes |
Parameter modifiers (ref, out, in) | Yes |
| Return type | Yes (with one exception in the Covariant Return Types lesson) |
| Access modifier | Yes |
If anything is off, the compiler rejects the override. A few of the most common slip-ups:
The base method takes no parameters; the override takes one. The parameter lists differ, so there's no virtual method matching this signature. The fix is to either match the base signature exactly or expose the new behavior under a different name.
Return type also has to match:
Even though double and decimal are both numeric, they're different types, and override requires an exact return type match. Covariant return types relax this slightly, but only for return types that are derived from the base return type, never for unrelated types like double.
Access modifiers must match too:
The base method is public; the override has to be public as well. C# does not let you tighten or loosen accessibility on an override, because the base method's contract already promised a particular visibility, and changing it would break callers that rely on that promise.
The signature rule is what makes override safer than the naive "two methods that happen to share a name" approach. If you typo the parameter list or the return type, the compiler tells you immediately instead of silently producing a new method that shadows the base.
Cost: override itself adds nothing at runtime. The compiler still emits one method body for each version. The cost (an indirect call through the method table) comes from virtual, not from override, and is paid whether or not anyone actually overrides the method. The _Runtime Polymorphism_ lesson unpacks the dispatch mechanism.
C# is opinionated here. By default, methods are non-virtual, and the base class author has to opt in to being overridable by writing virtual. Some languages (Java, for example) go the other way and make every method overridable unless you say otherwise.
The C# stance is informed by Eric Lippert's blog and the long-running advice from .NET's framework guidelines: a method that can be overridden is a method whose behavior the base class doesn't fully control. Every virtual method is an extension point, and every extension point is a contract the base author has to support forever. Once the method is virtual, derived classes can change its behavior in ways the base class can't anticipate, and any internal code that calls the method has to account for the possibility that some derived class will do something unexpected.
This is the "designed for inheritance" philosophy. Making something virtual is a deliberate decision: "I'm promising that this method is meant to be customized, and I'm taking responsibility for the contract." Leaving it non-virtual is the safer default, because you can always make a non-virtual method virtual later (additive change), but you can't take virtual away from a public method without breaking every override that exists in the wild.
A Product.CalculatePrice that's virtual is a statement: "subclasses are expected to compute price differently." A Product.GetSku that's not virtual is a statement: "the SKU is whatever I say it is; subclasses don't get to change that." Both are valid design choices, and C# wants you to pick on purpose.
baseAn override doesn't have to throw away the base behavior. Sometimes you want to extend it: do what the base does, then add something on top. The base keyword (covered in detail in the _base Keyword_ lesson) lets the override call the original method by name.
A real example. A Product formats itself one way. A DiscountedProduct should format itself the same way, then append the discount.
Inside the override, base.Describe() runs the original Product.Describe body. The override then takes that string and adds the discount suffix. If Product.Describe changes tomorrow (say, it starts including the currency code), the derived class picks up the change without any edits.
The base keyword always refers to the immediate parent's implementation. It is resolved at compile time, not at runtime, so base.Method() doesn't go through virtual dispatch even if the method is virtual. This is the one situation in C# where a virtual call is bypassed deliberately.
A common mistake is forgetting to call base.Method() when extending behavior:
If the intent was to extend, this is a bug: the ($price) part of the base description is gone. If the intent was to replace, this is fine, but most overrides that extend behavior end up calling base.Method() at some point in the body, typically as the first or last line.
Inheritance can go more than one level deep, and so can overriding. A grandparent declares a method virtual, a parent overrides it, and a grandchild overrides it again. The chain works as you'd expect: each override replaces the inherited implementation for its own type and all its descendants.
The diagram below shows the dispatch path. Each class in the hierarchy has its own version of CalculatePrice, and the runtime picks the lowest one that matches the object's actual type.
The diagram shows three classes in an inheritance line. Each one has its own body for CalculatePrice. When the method is called on an instance, the runtime walks down to the most-derived override and runs that one.
The code matches the diagram:
Notice the keyword on ClearanceProduct.CalculatePrice is still override, not virtual. The method is already virtual all the way down the chain. The grandparent introduced it as virtual; from then on, every replacement uses override. Once a method is in the virtual chain, it stays there.
A subtle but useful consequence: even when ClearanceProduct.CalculatePrice is called on a variable typed as Product, the clearance version runs. The runtime always picks the most-derived override based on the object's actual type, not the variable's declared type.
sealed overrideThere are times when you want to override a method but also prevent any class deeper in the hierarchy from overriding it further. The sealed override combination does exactly that.
SpecialEdition inherits CalculatePrice from FixedPriceProduct, but it can't override it because the parent sealed it. SpecialEdition.CalculatePrice() runs the FixedPriceProduct body.
sealed override is a "this far, no further" marker. It says the chain ends here. This is most useful when a method is virtual for historical reasons, but a particular subclass wants to lock in a specific behavior to prevent subclasses from changing it. Lesson 6 in this section covers sealed in more depth, including how it applies to entire classes.
virtual and override aren't limited to methods. Properties also have getter and setter bodies, and those bodies can be overridden by derived classes the same way.
The base declares DisplayPrice as a virtual read-only property. The derived class overrides it with its own getter body. From the caller's side, accessing .DisplayPrice looks exactly the same as for any other property, but the body that runs depends on the object's actual type.
A few rules specific to property overrides:
virtual/override keyword goes on the property itself, not on the individual get or set accessors.get and set, the override replaces the whole property; you can't override just the getter or just the setter independently.public get; protected set;, the override has the same shape).{ get; set; }) cannot themselves be virtual in a useful way without a backing computation, but virtual works fine on expression-bodied properties and on full property syntax with getter/setter bodies.Indexers and events follow the same pattern. The virtual/override mechanism applies to any member with a body that runs when accessed.
Method overriding and method overloading sound similar and are often confused. They solve different problems and follow different rules. A side-by-side comparison clears it up:
| Aspect | Overriding | Overloading |
|---|---|---|
| Where it lives | Across classes (base + derived) | Within a single class |
| Goal | Replace inherited behavior | Same name for different input shapes |
| Method names | Must be the same | Must be the same |
| Parameter lists | Must be the same | Must differ |
| Return type | Must be the same (covariant in the Covariant Return Types lesson) | Doesn't matter for distinguishing |
| Keywords required | virtual on base, override on derived | None |
| Resolution timing | Runtime (based on actual object type) | Compile time (based on argument types) |
| Inheritance involved | Always | Not required |
The two can coexist. A base class can have a virtual method, and the derived class can both override it and add overloads of the same name with different parameter lists. Each kind plays by its own rules.
The first call has no arguments, so it matches the inherited (overridden) signature. The second call passes a decimal, which matches the overload. Overload resolution picks one of the two CalculatePrice methods at compile time; virtual dispatch then makes sure the right body runs for the override. The two mechanisms cooperate without confusing each other.
A worked example that uses everything from this lesson. The base class is Product. DiscountedProduct overrides both the price calculation and the description. ClearanceProduct does another override on top of that.
Three different price calculations from three different override bodies. The Describe method on DiscountedProduct extends the base description by calling base.Describe(), then appending the discount tag. ClearanceProduct doesn't override Describe, so it inherits the DiscountedProduct version, which still runs CalculatePrice through the virtual chain and gets the clearance price.
This is the toolkit virtual and override give you:
virtual).override).base.Method()).sealed override.What's intentionally left for later:
virtual and override are the canonical, type-safe way to replace inherited behavior. The compiler checks signatures, the runtime picks the right body, and the base class stays in control of which methods are open to customization. There is one other way to declare a method in a derived class with the same name as a base method, and it behaves very differently from override.
virtual/override is true overriding: the derived body wins regardless of how you reach the object. But what if the base class wasn't marked virtual to begin with, and you still want a derived version with the same name? Or what if you want a derived method that intentionally hides the base, not replaces it? That's where the new keyword comes in, and the behavior is subtly different.
virtual keyword; a derived method opts into replacing it with the override keyword. Both halves are required.virtual is the explicit opt-in.base.Method() inside an override to call the parent's implementation. This is resolved at compile time and bypasses virtual dispatch deliberately.virtual at one level stays in the virtual chain; deeper classes keep using override, and the most-derived body wins at runtime.sealed override lets a derived class lock the method against any further override down the chain.virtual/override keyword goes on the member itself, not on the individual accessors.The _new Keyword (Method Hiding)_ lesson covers what happens when a derived class declares a method with the same name as a base method that isn't marked virtual, why the behavior is different from override, and when (if ever) hiding is actually the right tool.