Understanding Method Shadowing and Overriding in C# Inheritance

Separating Myth from Reality

Myth: The new and override keywords in C# are interchangeable ways to replace base class methods in derived classes. Reality: They produce fundamentally different behaviors that directly affect polymorphism and which method executes at runtime.

This misconception causes subtle bugs in inheritance hierarchies. You might write a derived class method expecting polymorphic behavior, only to find the base method executes when you store instances in base class variables. Or you might accidentally break the polymorphic chain when you meant to extend it. Understanding the difference is critical for writing correct object-oriented code.

This article explains how overriding provides true polymorphism while shadowing hides base members without polymorphic dispatch. You'll see practical examples of both, understand when each applies, and learn how reference types affect which method gets called.

Method Overriding: True Polymorphism

Method overriding with the override keyword enables polymorphism. When a base class marks a method as virtual (or abstract), derived classes can override it to provide specialized implementations. The runtime determines which method to call based on the object's actual type, not the reference type.

This is the foundation of polymorphic behavior in object-oriented programming. You can treat different types uniformly through base class references, and each object executes its own implementation. This enables extensibility and loose coupling.

OverridingExample.cs
public class Shape
{
    // Base method marked as virtual to allow overriding
    public virtual double CalculateArea()
    {
        return 0;
    }

    public virtual string GetDescription()
    {
        return "Generic shape";
    }
}

public class Circle : Shape
{
    public double Radius { get; set; }

    // Override provides polymorphic behavior
    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }

    public override string GetDescription()
    {
        return $"Circle with radius {Radius}";
    }
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public override double CalculateArea()
    {
        return Width * Height;
    }

    public override string GetDescription()
    {
        return $"Rectangle {Width}x{Height}";
    }
}

// Polymorphic behavior in action
Shape shape1 = new Circle { Radius = 5 };
Shape shape2 = new Rectangle { Width = 10, Height = 20 };

// Calls Circle.CalculateArea() - polymorphic dispatch
Console.WriteLine($"{shape1.GetDescription()}: {shape1.CalculateArea():F2}");

// Calls Rectangle.CalculateArea() - polymorphic dispatch
Console.WriteLine($"{shape2.GetDescription()}: {shape2.CalculateArea():F2}");

// Output:
// Circle with radius 5: 78.54
// Rectangle 10x20: 200.00

Even though shape1 and shape2 are declared as Shape references, the runtime calls the correct overridden methods based on the actual object type. This is polymorphism—the ability to treat different types uniformly while preserving their specific behavior.

Method Shadowing: Breaking Polymorphism

Method shadowing with the new keyword hides a base class member rather than overriding it. The hidden method doesn't participate in polymorphic dispatch. Which method executes depends on the reference type, not the object's runtime type.

Shadowing is useful when you inherit from a class you can't modify and need to introduce a member with the same name but completely different semantics. However, it breaks polymorphism, so use it deliberately and sparingly.

ShadowingExample.cs
public class Employee
{
    public virtual void DisplayInfo()
    {
        Console.WriteLine("Employee information");
    }

    public string GetRole()
    {
        return "Generic Employee";
    }
}

public class Manager : Employee
{
    // Shadowing with new keyword - breaks polymorphism
    public new void DisplayInfo()
    {
        Console.WriteLine("Manager information");
    }

    // Also shadowing the non-virtual GetRole
    public new string GetRole()
    {
        return "Manager";
    }
}

// Demonstrating shadowing behavior
Manager mgr = new Manager();
Employee emp = mgr; // Same object, different reference type

// Reference type determines which method is called
mgr.DisplayInfo();  // Calls Manager.DisplayInfo()
emp.DisplayInfo();  // Calls Employee.DisplayInfo() - NOT polymorphic!

Console.WriteLine(mgr.GetRole());  // "Manager"
Console.WriteLine(emp.GetRole());  // "Generic Employee" - based on reference type

// Output:
// Manager information
// Employee information
// Manager
// Generic Employee

This example shows the key difference: with shadowing, the reference type (Employee vs Manager) determines which method executes, not the object's actual type. Both emp and mgr reference the same Manager object, but calling through the Employee reference executes the Employee version. This is not polymorphic behavior.

Direct Comparison: Override vs New

Let's see both approaches side by side with the same inheritance hierarchy. This clearly demonstrates how override maintains polymorphism while new breaks it.

Comparison.cs
public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Some generic animal sound");
    }

    public virtual void Move()
    {
        Console.WriteLine("Animal moves");
    }
}

public class Dog : Animal
{
    // Override - polymorphic behavior
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }

    // Shadowing - breaks polymorphism
    public new void Move()
    {
        Console.WriteLine("Dog runs");
    }
}

// Testing polymorphic vs non-polymorphic behavior
Dog dog = new Dog();
Animal animalRef = dog;  // Same object, base class reference

Console.WriteLine("=== Called through Dog reference ===");
dog.MakeSound();  // "Woof!" - calls Dog's version
dog.Move();       // "Dog runs" - calls Dog's version

Console.WriteLine("\n=== Called through Animal reference ===");
animalRef.MakeSound();  // "Woof!" - polymorphic, calls Dog's version
animalRef.Move();       // "Animal moves" - NOT polymorphic, calls Animal's version

// Output:
// === Called through Dog reference ===
// Woof!
// Dog runs
//
// === Called through Animal reference ===
// Woof!
// Animal moves

MakeSound demonstrates correct polymorphism—regardless of reference type, the Dog implementation executes. Move demonstrates shadowing—the reference type determines which implementation runs. This is why override is almost always what you want for inheritance hierarchies.

Try It Yourself

Here's a complete example that demonstrates both shadowing and overriding in a payment processing scenario. Run this to see how reference types affect method dispatch.

Program.cs
using System;

namespace ShadowingVsOverriding;

public class PaymentProcessor
{
    public virtual void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing payment: ${amount}");
    }

    public virtual decimal CalculateFee(decimal amount)
    {
        return amount * 0.02m; // 2% default fee
    }

    public void LogTransaction(decimal amount)
    {
        Console.WriteLine($"Transaction logged: ${amount}");
    }
}

public class CreditCardProcessor : PaymentProcessor
{
    // Override - polymorphic behavior for fee calculation
    public override decimal CalculateFee(decimal amount)
    {
        return amount * 0.03m; // 3% for credit cards
    }

    // Shadow - non-polymorphic for payment processing
    public new void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Credit card charge: ${amount}");
    }

    // Shadow the logging method
    public new void LogTransaction(decimal amount)
    {
        Console.WriteLine($"Credit card transaction logged: ${amount}");
    }
}

class Program
{
    static void Main()
    {
        var ccProcessor = new CreditCardProcessor();
        PaymentProcessor baseRef = ccProcessor;

        decimal amount = 100m;

        Console.WriteLine("=== Through CreditCardProcessor reference ===");
        ccProcessor.ProcessPayment(amount);
        Console.WriteLine($"Fee: ${ccProcessor.CalculateFee(amount):F2}");
        ccProcessor.LogTransaction(amount);

        Console.WriteLine("\n=== Through PaymentProcessor reference ===");
        baseRef.ProcessPayment(amount);  // Shadowed - calls base version!
        Console.WriteLine($"Fee: ${baseRef.CalculateFee(amount):F2}");  // Overridden - polymorphic!
        baseRef.LogTransaction(amount);  // Shadowed - calls base version!
    }
}

// Output:
// === Through CreditCardProcessor reference ===
// Credit card charge: $100
// Fee: $3.00
// Credit card transaction logged: $100
//
// === Through PaymentProcessor reference ===
// Processing payment: $100
// Fee: $3.00
// Transaction logged: $100

Save this as Program.cs and create a project file:

ShadowingVsOverriding.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>

Run with: dotnet run

When to Use Override vs New

Choosing between override and new affects how your inheritance hierarchy behaves. The decision should be deliberate, not accidental.

Use override when: You're extending or specializing a base class method. This is the default choice for inheritance. Override maintains polymorphism, allowing collections of base class references to execute the correct derived implementation. Use this for template methods, strategy patterns, and any scenario where you want polymorphic behavior.

Use new when: You explicitly want to hide a base member and break the polymorphic chain. This is rare. The main scenario is when you inherit from a class you can't modify, and it has a method whose name matches your needs but whose semantics don't fit. You create a completely different method that happens to share the name.

Warning about implicit shadowing: If you define a method with the same signature as a base method but don't use override or new, C# issues a warning (CS0108) and treats it as new. This can cause bugs. Always explicitly use override or new to clarify your intent. If you meant to override but the base method isn't virtual, you need to reconsider your design.

Sealed override: You can mark an override method as sealed to prevent further overriding in derived classes. Use this when you want to provide a definitive implementation that shouldn't be changed. This is less common than virtual methods but useful for security-critical code or finalized algorithms.

Gotchas and Common Mistakes

Method shadowing and overriding have subtle behaviors that can trip up even experienced developers. Understanding these edge cases prevents bugs in your inheritance hierarchies.

Forgetting the override keyword: If you intend to override but forget the keyword, you accidentally shadow the method instead. The compiler warns you, but the code compiles. When you call the method through a base reference, the base implementation executes instead of yours. Always use override explicitly.

Trying to override non-virtual methods: You can only override methods marked virtual, abstract, or override. If the base method isn't designed for polymorphism, you can't override it—you can only shadow it with new. If you need polymorphic behavior, the base class design needs to change.

Changing accessibility: An overriding method cannot be less accessible than the virtual method it overrides. If the base method is public, the override must be public. This prevents breaking the Liskov Substitution Principle where derived classes should be usable anywhere the base class is expected.

Mixing new and override in inheritance chains: Once you shadow a method with new, derived classes see the shadowed version, not the original virtual method. This can create confusing hierarchies where polymorphism works unexpectedly. Avoid mixing shadowing and overriding in the same method across multiple inheritance levels.

Frequently Asked Questions (FAQ)

What's the main difference between shadowing and overriding in C#?

Overriding with the override keyword provides polymorphic behavior where the derived class method executes regardless of reference type. Shadowing with the new keyword hides the base method, and which method executes depends on the reference type, not the object's runtime type.

When should I use the new keyword instead of override?

Use new when you explicitly want to hide a base class member and break the polymorphic chain. This is rare but useful when you inherit from a class you can't modify and need a completely different implementation that shouldn't participate in polymorphism.

Can I override a method without the virtual keyword in the base class?

No, you can only override methods explicitly marked as virtual, abstract, or already override in the base class. Without these keywords, the base method isn't designed for polymorphism. You can use new to shadow it, but that's not true overriding.

What happens if I forget to use override or new keywords?

The compiler issues a warning (not an error) that you're hiding a base member. The code compiles but behaves as if you used new, which might not be your intention. Always explicitly use override or new to clarify your intent and avoid bugs.

Back to Articles