How the New Keyword Differs from Override in C# Inheritance

Myth vs Reality

Myth: The new and override keywords both let derived classes replace base class methods, so they're interchangeable. Reality: These keywords create fundamentally different behaviors that determine whether your polymorphism works correctly or breaks in subtle ways.

Override enables true polymorphism where the runtime determines which method to call based on the actual object type. New hides the base class method, creating separate method chains that depend on the reference type, not the object type. This distinction matters every time you cast objects to their base types.

You'll learn exactly how new and override differ, see the problems method hiding causes, and understand when each keyword applies. We'll cover virtual dispatch, method resolution at compile time versus runtime, and the warning signs that you've chosen the wrong approach.

How Override Enables Polymorphism

The override keyword replaces a base class method marked as virtual or abstract. When you call an overridden method through a base class reference, the runtime looks at the actual object type and calls the most derived implementation. This is polymorphism in action.

Virtual dispatch happens at runtime through the virtual method table. The CLR checks the object's type and invokes the appropriate override. This means casting a derived object to its base type doesn't change which method runs.

Shape.cs - Override example
public class Shape
{
    public virtual double CalculateArea()
    {
        return 0;
    }

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

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

    public Circle(double radius)
    {
        Radius = radius;
    }

    // Override replaces the base implementation
    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 Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

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

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

// Usage demonstrating polymorphism
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);

Console.WriteLine($"{circle.GetDescription()}: {circle.CalculateArea():F2}");
Console.WriteLine($"{rectangle.GetDescription()}: {rectangle.CalculateArea():F2}");

// Output:
// Circle with radius 5: 78.54
// Rectangle 4x6: 24.00

Even though circle and rectangle are declared as Shape references, the runtime calls the Circle and Rectangle implementations. The virtual keyword in the base class signals that derived classes can override the method. The override keyword in derived classes replaces the base implementation completely.

Understanding Method Hiding with New

The new keyword hides a base class member rather than overriding it. When you call a method marked with new, the compiler determines which version to use based on the reference type at compile time, not the actual object type at runtime. This breaks polymorphic behavior.

Method hiding creates two separate methods with the same name. The derived class method doesn't replace the base class method. Which one gets called depends entirely on how you reference the object.

Employee.cs - New keyword example
public class Employee
{
    public string Name { get; set; }

    public void PrintInfo()
    {
        Console.WriteLine($"Employee: {Name}");
    }

    public virtual void PrintRole()
    {
        Console.WriteLine("Role: General Employee");
    }
}

public class Manager : Employee
{
    public int TeamSize { get; set; }

    // Method hiding - breaks polymorphism
    public new void PrintInfo()
    {
        Console.WriteLine($"Manager: {Name}, Team Size: {TeamSize}");
    }

    // Proper override - maintains polymorphism
    public override void PrintRole()
    {
        Console.WriteLine("Role: Manager");
    }
}

// Usage demonstrating the difference
var manager = new Manager { Name = "Alice", TeamSize = 5 };

// Direct call - uses Manager.PrintInfo
manager.PrintInfo();
manager.PrintRole();

// Base reference - PrintInfo uses Employee version (hidden method)
Employee employee = manager;
employee.PrintInfo();
employee.PrintRole(); // Uses Manager version (overridden method)

// Output:
// Manager: Alice, Team Size: 5
// Role: Manager
// Employee: Alice
// Role: Manager

Notice how PrintInfo behaves differently based on the reference type. When called through the Manager reference, it uses the Manager implementation. When called through the Employee reference, it uses the Employee implementation even though the object is still a Manager. PrintRole works polymorphically because it uses override instead of new.

Direct Comparison: New vs Override

The difference between new and override becomes obvious when you put them side by side. Let's see both keywords in action on the same base class to understand their behavior completely.

This example shows how the choice between new and override affects method dispatch. Pay attention to which methods get called when we use different reference types.

Vehicle.cs - Comparing new and override
public class Vehicle
{
    public virtual void Start()
    {
        Console.WriteLine("Vehicle starting...");
    }

    public virtual void Stop()
    {
        Console.WriteLine("Vehicle stopping...");
    }
}

public class Car : Vehicle
{
    // Override - participates in polymorphism
    public override void Start()
    {
        Console.WriteLine("Car engine ignition...");
    }

    // New - hides base method
    public new void Stop()
    {
        Console.WriteLine("Car brakes applied...");
    }
}

// Test different reference types
Car car = new Car();
Vehicle vehicle = car;

Console.WriteLine("Called through Car reference:");
car.Start();  // Polymorphic call
car.Stop();   // Direct call to Car.Stop

Console.WriteLine("\nCalled through Vehicle reference:");
vehicle.Start();  // Polymorphic - calls Car.Start
vehicle.Stop();   // Non-polymorphic - calls Vehicle.Stop

Console.WriteLine("\nExplicit base call:");
((Vehicle)car).Stop(); // Forces Vehicle.Stop

// Output:
// Called through Car reference:
// Car engine ignition...
// Car brakes applied...
//
// Called through Vehicle reference:
// Car engine ignition...
// Vehicle stopping...
//
// Explicit base call:
// Vehicle stopping...

Start uses override, so it works polymorphically. Stop uses new, so the reference type determines which method runs. This inconsistency confuses developers and creates bugs when objects are passed through base class interfaces or collections.

When to Use Each Keyword

In almost all cases, you want override. It provides the polymorphic behavior that object-oriented design expects. When you create a collection of base class references, override ensures the correct derived implementations run.

The new keyword has limited legitimate uses. You might use it when extending a sealed library class that happens to have a method with the same name you need. Or when explicitly creating a new inheritance chain that's independent of the base class method. These situations are rare.

Program.cs - Practical example
public abstract class PaymentMethod
{
    public abstract decimal CalculateFee(decimal amount);

    public virtual string GetDetails()
    {
        return "Payment method";
    }
}

public class CreditCard : PaymentMethod
{
    public string CardNumber { get; set; }

    // Override abstract method - required
    public override decimal CalculateFee(decimal amount)
    {
        return amount * 0.029m; // 2.9% fee
    }

    // Override virtual method - provides polymorphic behavior
    public override string GetDetails()
    {
        return $"Credit Card ending in {CardNumber.Substring(CardNumber.Length - 4)}";
    }
}

public class PayPal : PaymentMethod
{
    public string Email { get; set; }

    public override decimal CalculateFee(decimal amount)
    {
        return amount * 0.034m + 0.30m; // 3.4% + $0.30
    }

    public override string GetDetails()
    {
        return $"PayPal account: {Email}";
    }
}

// Process payments polymorphically
List payments = new()
{
    new CreditCard { CardNumber = "1234567890123456" },
    new PayPal { Email = "user@example.com" }
};

decimal amount = 100m;

foreach (var payment in payments)
{
    decimal fee = payment.CalculateFee(amount);
    Console.WriteLine($"{payment.GetDetails()}");
    Console.WriteLine($"  Fee for ${amount}: ${fee:F2}\n");
}

// Output:
// Credit Card ending in 3456
//   Fee for $100: $2.90
//
// PayPal account: user@example.com
//   Fee for $100: $3.70

This payment processing example shows proper use of override. The code processes different payment methods through a common interface. Each payment type calculates its fee differently, but the calling code doesn't need to know about specific types. This is the power of polymorphism that override provides.

Try It Yourself

Here's a complete example you can run to see the behavior differences firsthand. This demonstrates both correct polymorphic design and the problems method hiding causes.

InheritanceDemo.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>
Program.cs - Complete demonstration
public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Some generic animal sound");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
}

public class Cat : Animal
{
    // Warning: Using new instead of override
    public new void MakeSound()
    {
        Console.WriteLine("Meow!");
    }
}

List animals = new()
{
    new Dog(),
    new Cat()
};

Console.WriteLine("Polymorphic calls through Animal reference:");
foreach (var animal in animals)
{
    animal.MakeSound();
}

Console.WriteLine("\nDirect calls:");
var dog = new Dog();
var cat = new Cat();
dog.MakeSound();
cat.MakeSound();

// Output:
// Polymorphic calls through Animal reference:
// Woof!
// Some generic animal sound
//
// Direct calls:
// Woof!
// Meow!

Notice how the Cat's MakeSound method doesn't work polymorphically. When called through the Animal reference, it uses the base class implementation. This is almost never what you want. Run this code and experiment by changing Cat to use override instead of new to see proper polymorphic behavior.

Design Guidelines

Always prefer override over new when working with inheritance. If the base class method is virtual or abstract, use override to provide the derived implementation. This gives you proper polymorphic behavior and matches developer expectations.

If you find yourself wanting to use new, question whether inheritance is the right approach. Composition might serve you better. When you hide a method with new, you're saying the derived class isn't really a specialized version of the base class, which breaks the is-a relationship that inheritance represents.

The compiler warns you when you declare a method that hides a base class member without explicitly using new. Don't ignore this warning. Either use override to participate in polymorphism or explicitly use new to document that you're intentionally hiding the base method.

Mark base class methods as virtual only if you intend for derived classes to override them. If a method shouldn't be overridden, don't mark it virtual. This prevents accidental hiding and makes your inheritance design explicit. Use sealed on overridden methods when you want to stop further overriding in the inheritance chain.

Frequently Asked Questions (FAQ)

When should I use new instead of override?

Use new when you intentionally want to hide a base class member without participating in polymorphism. This is rare in good design. Use override when you want polymorphic behavior where the derived class method runs even when called through a base class reference. Override is almost always the right choice.

What happens if I omit both new and override?

The compiler generates a warning and treats it as if you used new. The method hides the base class member without overriding it. Always be explicit by using either new or override to make your intent clear and silence compiler warnings.

Can I override a method marked with new?

Yes, if the new method is also marked virtual. The new keyword starts a new inheritance chain for that method. Derived classes can then override this new virtual method, but this override chain is separate from the original base class method's override chain.

Back to Articles