Last Updated: May 17, 2026
virtual and override cooperate to let a derived class replace a base method, with the runtime picking the right version based on the actual object. C# has a second mechanism that looks similar on the surface but behaves very differently: method hiding, controlled by the new modifier. This chapter walks through what hiding is, when it happens, why the compiler warns you about it, and the dispatch surprise that catches even experienced C# developers off guard.
Method hiding happens when a derived class declares a method with the same signature as one in its base class, without the base method being virtual. The derived method doesn't override the base method, because there's nothing to override. It just introduces a new method that shadows the base member when you access it through a reference of the derived type.
Start with a base class that has a non-virtual method:
Nothing surprising. Describe runs and prints the product name. Now imagine a DigitalProduct subclass wants its own way of describing itself, but it can't change Product because Describe isn't virtual. If you simply declare Describe again in the subclass, C# allows it, but the compiler is going to flag the situation:
The code compiles, but the compiler emits warning CS0108:
The compiler is telling you: "you wrote a method with the same name as one in the base class, and you didn't say whether this was on purpose. If you meant to hide, please be explicit." That's where the new modifier comes in.
newThe new keyword (used as a member modifier, not the object-creation operator) is how you tell the compiler "yes, I know I'm hiding the base member, and that's exactly what I want."
The behavior is identical to the version without new. The only difference is that the warning goes away, because you've stated your intent clearly. Hiding works exactly the same whether you write new or not. The keyword is documentation for the reader and the compiler, not a switch that changes runtime behavior.
A small comparison helps lock this in:
| Form | Behavior | Compiler reaction |
|---|---|---|
| Same signature in derived class, no modifier | Hides the base method | Warning CS0108 |
Same signature with new modifier | Hides the base method | No warning |
Same signature with override, base is virtual | Overrides the base method | No warning |
Same signature with override, base is not virtual | Compile error CS0506 | Error |
The new modifier syntax sits where override would sit:
Here's where hiding diverges from overriding in a way that trips people up. With virtual + override, the runtime picks which method runs based on the actual type of the object, no matter what kind of reference you hold. With new (hiding), the compiler picks which method runs based on the type of the reference, before the program even starts.
The clearest way to see it is two parallel hierarchies, one using virtual/override, one using new. Compare the output for the exact same calling pattern.
First, the override version:
The reference type is CartItem, but the actual object is a GiftCard. Because PrintLabel is virtual and GiftCard overrides it, the runtime walks down to the most-derived implementation and calls GiftCard.PrintLabel.
Now the hiding version, identical except for the modifiers:
Same object on the heap (a GiftCard). Same call. Different result. The compiler looked at the declared type of item, saw CartItem, and bound the call to CartItem.PrintLabel. It never asks the runtime "what type is item actually pointing at?" because the call isn't virtual.
Cast the reference and the answer flips:
Same object, two references, two different method bodies. This is the dispatch surprise in one example. With hiding, the answer depends on the type you used to look at the object, not what the object actually is.
A diagram makes the contrast concrete:
The mechanism in one sentence: override participates in runtime dispatch, new participates in compile-time dispatch. The reference type tells the compiler which method to bind to when you hide; the object type tells the runtime which method to call when you override.
The two mechanisms sit next to each other in C# and use similar syntax, so it pays to lay out the differences precisely.
| Aspect | virtual + override | new (hiding) |
|---|---|---|
| Base method requires | virtual, abstract, or override | Anything (or nothing); the base is unchanged |
| Derived method modifier | override | new |
| Dispatch | Runtime, based on object's actual type | Compile-time, based on reference type |
| Polymorphism participation | Yes, full polymorphic dispatch | No, breaks polymorphism for the hidden member |
| Reachable via base reference | Always the derived version | Always the base version |
| Compiler warning if modifier omitted | None (overriding requires the keyword) | CS0108 warns about accidental hiding |
| Typical use case | Designed-for-extension behavior in a class hierarchy | Rare; mostly defensive against base-class changes |
| IL opcode emitted | callvirt (virtual dispatch) | call (direct dispatch) |
The row that matters most for understanding is dispatch. Everything else in the table follows from that single difference. Once you internalize "override means the runtime decides, new means the compiler decides," predicting any hiding-related output becomes a matter of asking which reference is in use.
newMost of the time, you don't want hiding. If you're designing two classes from scratch and one extends the other, virtual/override is the right tool. Hiding shows up mainly in three situations.
The first is defensive coding against a base class you don't control. You inherit from a library class, add a helper method called Validate, and ship the code. A year later the library author releases a new version that adds its own Validate method to the base class. Your code suddenly has two Validate methods with the same signature, and the compiler emits CS0108. Adding new to your derived method tells the compiler "yes, I know, my method continues to do what it did before, ignore the base version." This is the most common real-world reason to use new.
The second case is changing the return type or signature in a way that override can't handle. Override requires the derived method to match the base method's signature exactly (modulo covariant return types). If you genuinely need a different return type or parameter list and the language won't let you override, hiding gives you a way out, at the cost of breaking polymorphism for that member.
The third case is intentionally cutting off polymorphism for a specific call site. This is unusual and almost always a smell, but in narrow contract scenarios where a derived class needs to expose its own non-virtual answer for code that explicitly uses the derived reference, hiding lets you do that. Code review should ask "are you sure?" every time.
What you shouldn't do is reach for new because you wanted to override but the base method wasn't virtual. That impulse usually means the base class wasn't designed for extension, and hiding will produce confusing behavior the moment someone calls the method through a base-class reference. The honest options are: ask the base-class author to make the method virtual, redesign the relationship (composition instead of inheritance), or accept the base behavior.
Methods aren't the only members you can hide. The new modifier applies to anything declared in a derived type that shadows a name from the base type: properties, fields, events, nested types, and constants.
A property example:
Same dispatch rule: the reference type decides. s1 is declared as Subscription, so Subscription.Tier is read. s2 is declared as PremiumSubscription, so the hidden property is read. Properties can also be virtual/override, and the same difference applies as with methods.
A field example shows the same effect on raw fields:
Wait, the second line says p.Category again, but the value is different. That's a copy-paste in the print labels; both lines access different fields. The p variable is a ProductBase reference, so p.Category reads the base Category field. The b variable is a Book reference, so b.Category reads the hidden Category field. Two physically different fields exist on the object in memory, both called Category, and the reference type picks which one is in scope.
Hiding fields like this is almost always a bad idea. Each instance now carries both fields, the names collide for readers, and the behavior is purely compile-time. Treat the field example as a warning, not a pattern to imitate.
Nested types can also be hidden:
Storefront.Banner and HolidayStorefront.Banner are two unrelated nested types that share a name, with new declaring the second one as deliberately replacing the visible name on the derived type. You'll rarely use this in practice, but it's part of the language.
Cost: Hiding a member doesn't free up memory or remove the base member. The hidden field, property, or method still exists on the object and can be accessed through a base reference or by using base.Member from inside the derived class. Hiding is name resolution, not deletion.
Bring the pieces together with an e-commerce price-display example. The base class defines a non-virtual Format method that subclasses might want to customize. Compare what happens with hiding versus what you'd want with overriding.
Look at what the loop produced. Two of the three items are sale items, but the loop printed plain prices for all three because the loop iterates over PriceTag references and Format was hidden, not overridden. The compiler bound every tag.Format() call to PriceTag.Format at compile time, and the runtime didn't get a vote. Only the explicit SalePriceTag reference produced the richer "was $X" output.
If PriceTag.Format had been declared virtual and SalePriceTag.Format had used override, every line in the loop would have printed the appropriate sale text. This is exactly the moment where the design choice between hiding and overriding becomes visible. Hiding is fine for one-off variants accessed through their own type. Overriding is what you want for collections of mixed types where polymorphism is the point.
Inside a derived class, you can still reach the hidden base member when you need both behaviors. The base keyword gives you access to the base class's version even when your derived class has hidden it:
The pattern matches what you'd do inside an overridden method: call base.Method() to invoke the parent's logic, then layer on your own. From outside the class, base-class access through a derived reference isn't possible, but you can always cast a derived reference to the base type to reach the base version explicitly (which is the same dispatch rule we've been seeing).
A small extra detail worth knowing: a derived class can mark its hiding method virtual again, starting a new override chain rooted in the derived class.
This is unusual and rarely the right design, but the language allows it: EmailNotification.Send hides Notification.Send and re-introduces virtual dispatch from that point downward. WelcomeEmailNotification.Send then participates in polymorphism among EmailNotification references but not among Notification references. Most teams treat this as a code smell and rework the hierarchy instead.
Two mistakes show up often enough to be worth naming.
The first is accidentally hiding when you meant to override. Picture a class with a method called Save. A subclass adds its own Save method and forgets to mark it override:
The compiler emits warning CS0114:
The warning is specific to this case: the base method is virtual (overridable) but the derived method has no modifier. The compiler is telling you that the derived method silently became a hiding method, breaking polymorphic dispatch. The fix is almost always to add override. The fact that the language picks "hide" as the default behavior when the modifier is missing is a deliberate safety choice, but treat the warning as an error and fix it.
The second is forgetting that hiding doesn't carry through casts. Code that looks fine when written using derived references stops behaving as expected the moment it gets passed through a method that takes a base reference:
The caller passes a FeaturedItem, but the ShowItem method takes an Item, so inside the method the reference type is Item. The hidden method never runs. If DisplayInfo had been virtual/override, the output would be Featured item, because the runtime would have picked the most-derived implementation regardless of the method parameter's declared type. This is the same dispatch issue, just dressed up as a method call.
The diagnostic question to ask whenever you see this kind of bug: "Which method does the compiler see at this call site, and is the call virtual?" If the answer is "the base version, and no," hiding is the cause.
Whenever a derived class shares a method name with its base, ask three questions in order.
First, is the base method virtual, abstract, or already an override? If yes, you should almost certainly override it. The base class was designed for extension at this point, and polymorphism is what its author expected.
Second, do you control the base class? If yes, and you want the derived behavior to be used everywhere the method is called (including through base references), change the base method to virtual and override it. This is the right answer most of the time.
Third, is there a specific reason you want the base behavior to remain visible through base references? Examples include a library upgrade that introduced a name clash, or a deliberate decision to keep the base method as the canonical "shared" behavior for code that doesn't know about your subclass. If yes, use new and document why.
If none of the above apply, you probably don't want to share the name at all. Pick a different method name in the derived class. Two distinct methods with clear, separate names are easier to read than one hidden method, and they remove the dispatch ambiguity entirely.
| Situation | Recommended approach |
|---|---|
Base method is virtual, derived behavior should replace it for all callers | override |
| Base method is non-virtual, you control it, derived behavior should replace it | Make base virtual, then override |
| Base method is non-virtual, you don't control it, derived behavior is purely for the subclass's own consumers | new (with a comment explaining why) |
| Library added a method that collides with yours after the fact | new to suppress the warning, keep your behavior |
| You wanted different behavior but it conflicts conceptually with the base | Use a different method name; don't share the name at all |
virtual. The derived member shadows the base member when accessed through a derived reference.new modifier (used as a member modifier, not the object-creation operator) makes hiding explicit. Without it, the compiler emits warning CS0108. Behavior is identical with or without the keyword.override, dispatch is based on the object's actual type at runtime. With new, dispatch is based on the reference type at compile time. Same object, different reference, different method.Parent p = new Child() calls Parent.M() when M is hidden, and Child.M() when M is overridden. This is the central surprise the lesson hinges on.new rarely, mostly defensively, when a base-class upgrade introduces a name clash you can't avoid. In most other cases, prefer override (after making the base method virtual) or pick a different method name entirely.CS0108 (implicit hiding, use new), CS0114 (you hid a virtual method without saying so, probably meant override), CS0506 (tried to override a non-virtual method).base.Method(), the same way you would inside an overriding method.Hiding and overriding both deal with method dispatch in inherited classes. Constructors work differently, they always run from the top of the hierarchy down. The next chapter, Constructor Chaining, looks at how constructor chains work, what the runtime does when you instantiate a derived class, and how to pass arguments up through base(...) calls.