C# Inheritance — Missing Override Causes Silent Failure
override on a new method silently applied base premium discount logic instead of derived.- Inheritance models an 'is-a' relationship; if that doesn't hold, use composition.
- Use
overridefor polymorphic behaviour, nevernewunless you know what you're doing. - Always call base constructor explicitly using
: base(...).
- Inheritance in C# models an "is-a" relationship between classes
- Derived class inherits all non-private members from the base class
- Use
overrideto replace base behaviour; usenewto hide it - Base constructor must be explicitly called unless it's parameterless
- Deep hierarchies (5+ levels) are a smell — prefer composition
- Mistaking
newforoverridesilently breaks polymorphic dispatch
Inheritance Quick Debug Cheat Sheet
Derived method not called via interface or base reference
Search code: `grep -rn "new.*MethodName" ./src`Add `override` and make base method `virtual`Base constructor throws NullReferenceException
Examine `: base(...)` parametersAdd null checks before passing to baseAbstract class instantiation at runtime
Use `is` pattern to check actual type: `if (obj is ConcreteType)`Refactor to use factory patternSealed class used as base unexpectedly
Check class definition for `sealed` keywordIf you can't modify, use composition (wrap the sealed class)Method signature mismatch in derived class
Check base method signature (parameter types, return type)Use IDE to generate override: type `override` and spaceProduction Incident
new instead of override. The base method was not marked virtual, so override wasn't possible. When called via a base-class reference (Base ref = new Derived()), the base method was invoked because new does not participate in polymorphism.virtual.
2. Use override in the derived class.
3. Alternatively, ensure all callers use the derived type directly (defeats polymorphism).virtual.In the derived class, always use override — never new — unless you explicitly want to hide the base implementation (rare).Enable compiler warnings about 'new' keyword usage (CS0109) and treat them as errors in CI.Production Debug GuideDiagnose inheritance-related bugs fast in production
virtual. Check derived method uses override, not new. Use is and as to confirm runtime type.: base(...) syntax and ensure parameters are not null.out for covariance.new, both base.field and derived field exist separately. Use base.field to explicitly access base value.Every non-trivial C# application has classes that share behaviour. A Customer and an Employee both have a name, an email, and an ID. A Circle and a Rectangle both have an area and a perimeter. If you write those shared properties and methods twice, you've just signed up for double the maintenance, double the bugs, and double the pain every time requirements change. Inheritance is how C# lets you centralise what's shared and specialise what's different — and it's one of the four pillars of object-oriented programming for a very good reason.
The real problem inheritance solves isn't code length — it's conceptual integrity. When a BankAccount and a SavingsAccount share a base, you're modelling the real world accurately. A savings account IS a bank account. That 'is-a' relationship is the signal that inheritance belongs here. Without it, you end up with bloated utility classes, copy-pasted logic, and systems where fixing one bug means hunting down five nearly-identical methods spread across the codebase.
By the end of this article you'll understand how to build a clean inheritance hierarchy in C#, know the difference between virtual, override, and new (and why confusing them causes silent bugs that are a nightmare to trace), and be able to explain to an interviewer exactly when inheritance is the right tool — and when it absolutely isn't.
What is Inheritance in C#?
Inheritance is the mechanism by which a class (the derived class) can reuse and extend the members of another class (the base class). C# supports single class inheritance — a derived class can have only one direct base class. However, interfaces can be inherited multiple times. The syntax uses a colon: class Derived : Base. All non-private members (fields, properties, methods, events) are inherited. Constructors and finalizers are not inherited, but the derived class must call a base constructor. The protected access modifier is commonly used to share members with derived classes while hiding them from external code.
The real power of inheritance comes from polymorphism: you can treat a derived object as an instance of its base type. Code that operates on a base class can work with any derived class without modification. This is a fundamental concept in object-oriented design.
using System; namespace TheCodeForge.InheritanceDemo { public class Vehicle { public string Make { get; set; } public string Model { get; set; } public int Year { get; set; } public Vehicle(string make, string model, int year) { Make = make; Model = model; Year = year; } public virtual void Start() { Console.WriteLine($"{Make} {Model} started."); } } public class Car : Vehicle { public int Doors { get; set; } public Car(string make, string model, int year, int doors) : base(make, model, year) { Doors = doors; } public override void Start() { Console.WriteLine($"{Make} {Model} (car with {Doors} doors) started."); } } public class Program { public static void Main() { Vehicle myVehicle = new Car("Toyota", "Camry", 2020, 4); myVehicle.Start(); // Output: Toyota Camry (car with 4 doors) started. } } }
- If you can't say 'Dolphin IS A Mammal' truthfully, don't inherit.
- The base class captures common behavior and data; the derived class specializes.
- Polymorphism works because every derived object shares the base interface.
- If the relationship is 'has-a' instead of 'is-a', use composition over inheritance.
GetType() — don't trust the compile-time type alone.Base and Derived Classes: Syntax and Essentials
To create a derived class, place a colon and the base class name after the derived class name. The derived class automatically inherits all non-private members. However, constructors are not inherited: you must explicitly call a base constructor using : base(...). If the base class has a parameterless constructor, it's called implicitly — but it's still good practice to be explicit.
Access modifiers control visibility to derived classes. Use protected for members that should be accessible to derived types but not to external code. Use private protected (C# 7.2+) to allow access only to derived classes that are in the same assembly.
The base keyword inside a derived class allows you to access base class members even if they are overridden or hidden. It's especially useful when you want to extend a base method instead of replacing it completely.
using System; namespace TheCodeForge.InheritanceDemo { public class Animal { public string Name { get; set; } public Animal(string name) { Name = name; } public virtual void Speak() { Console.WriteLine("Animal makes a sound."); } } public class Dog : Animal { public Dog(string name) : base(name) { } public override void Speak() { base.Speak(); // Optional: call base implementation Console.WriteLine($"{Name} barks!"); } } class Program { static void Main() { Dog dog = new Dog("Rex"); dog.Speak(); } } }
Rex barks!
base.MethodName() to extend base behaviour cleanly.Virtual, Override, and New: The Critical Difference
The virtual keyword in the base class declares a method that can be overridden by a derived class. The override keyword in the derived class replaces the base implementation for all callers, including those using the base type reference. This is the heart of polymorphism.
The new keyword, when applied to a method with the same signature, hides the base method instead of overriding it. This means that the method called depends on the compile-time type of the variable, not the runtime type. This is a common source of subtle bugs.
A method that is not marked virtual cannot be overridden. If you attempt to use override on a non-virtual method, you get a compile-time error. You can still use new to shadow it, but that's usually a design smell.
When you override a method, you can call the base implementation using base.. This is useful when you want to add behaviour on top of the base class logic.MethodName()
new when you intended polymorphism results in the wrong method being called when the object is treated as its base type. This bug is silent — no compile-time warning by default. Enable IDE warning CS0109 and treat it as an error.new instead of override.dotnet_diagnostic.CS0109.severity = error.virtual in base, override in derivednew — but only if you understand the break in polymorphismnew — but reconsider design; consider compositionConstructors and Inheritance: Base Constructor Calls
Constructors are not inherited, but every derived class must ensure that the base class constructor is called. If the base class has a parameterless constructor, it's called implicitly. If the base class only has parameterized constructors, the derived class must explicitly call one using the colon syntax: public Derived(params) : base(params).
The base constructor runs before the derived constructor body. This means if you have initialization logic in both, the base runs first. Understanding this order is critical when properties are set in the base constructor and then modified in the derived constructor.
If the base constructor throws an exception, the derived constructor will not execute. This is a common cause of NullReferenceException when the derived class's field initializers run after the base constructor call (C# initializes fields before the base constructor call, but that's a subtle point).
Abstract Classes and Sealed Classes
An abstract class cannot be instantiated directly; it serves as a base class that defines a contract and optionally provides common implementation. Abstract methods have no body and must be overridden in any non-abstract derived class. Abstract classes can also have virtual methods, fields, constructors, etc.
A sealed class prevents further inheritance. It cannot be used as a base class. Sealing a class can help with security, performance (the JIT can devirtualize sealed class method calls), and design clarity.
Abstract classes are often used when you want to force derived classes to implement certain methods while providing shared base logic. They are similar to interfaces but can contain state (fields, properties with backing fields) and method bodies.
C# also allows you to seal individual methods in a derived class, preventing further overriding down the hierarchy.
using System; namespace TheCodeForge.InheritanceDemo { public abstract class Shape { public abstract double Area(); public void Display() { Console.WriteLine($"Area: {Area():F2}"); } } public sealed class Circle : Shape { public double Radius { get; } public Circle(double radius) { Radius = radius; } public override double Area() => Math.PI * Radius * Radius; } // This would cause compile error: 'cannot derive from sealed type' // public class ColoredCircle : Circle { } class Program { static void Main() { Shape shape = new Circle(5); shape.Display(); // Output: Area: 78.54 } } }
| Keyword | Effect | Polymorphism? | Typical Use Case |
|---|---|---|---|
| virtual | Allows derived class to override | Yes | Base methods expected to be customized |
| override | Replaces base virtual method | Yes | Providing specific behavior |
| new | Hides base method (shadowing) | No | When you must keep same name but don't want polymorphism (rare) |
| abstract | No implementation; forces derived override | Yes (when overridden) | Contract requiring derived to provide behavior |
| sealed | Prevents further override or inheritance | N/A | Security, performance, design clarity |
🎯 Key Takeaways
- Inheritance models an 'is-a' relationship; if that doesn't hold, use composition.
- Use
overridefor polymorphic behaviour, nevernewunless you know what you're doing. - Always call base constructor explicitly using
: base(...). - Seal classes and methods unless you explicitly expect inheritance.
- Deep inheritance hierarchies are a design smell; prefer flat structures.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QExplain the difference between
overrideandnewin C#.Mid-levelReveal - QHow does constructor chaining work in inheritance? Can a derived class prevent a base constructor from running?Mid-levelReveal
- QWhat is the difference between an abstract class and an interface in C#?SeniorReveal
- QCan you override a method that is not marked
virtualin the base class?JuniorReveal
Frequently Asked Questions
What is inheritance in C# in simple terms?
Inheritance lets a class (derived) automatically get all non-private members from another class (base). You can then add or modify behaviour. It's like a child inheriting traits from a parent, but with the ability to learn new skills.
Can a class inherit from multiple classes in C#?
No, C# supports only single class inheritance. However, a class can implement multiple interfaces. This avoids the diamond problem. If you need multiple base behaviours, use interfaces or composition.
What is the base keyword used for in C#?
base is used in a derived class to access members of the base class. It's commonly used to call base constructors (: ) or to invoke the base implementation of an overridden method (base()base.).SomeMethod()
What is the difference between hiding and overriding?
Hiding (with new) creates a new method with the same name; the original base method still exists. Which one gets called depends on the compile-time type of the variable. Overriding (with override) replaces the base method entirely so that the derived version is always called, regardless of the variable type. Hiding is non-polymorphic; overriding is polymorphic.
When should I use abstract classes vs interfaces?
Use an abstract class when derived classes share common state or implementation logic (e.g., a base Animal with a MakeSound method implemented for all animals). Use an interface when you want to define a contract that can be adopted by unrelated classes (e.g., IDisposable). Since C# 8, interfaces can have default implementations, but they cannot have fields or constructors.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.