C# Inheritance — Base, Override, Virtual, Sealed, and New Keyword
In this tutorial, you will learn about C# Inheritance. We cover key concepts, practical examples, and best practices to help you master this topic.
C# inheritance allows a class to derive from a base class, inheriting its members while enabling method overriding through virtual and override keywords for polymorphic behavior.
What You'll Learn
You will master inheritance in C#: creating class hierarchies with base and derived classes, using virtual and override for polymorphic methods, calling base class constructors and methods with the base keyword, preventing inheritance with sealed, hiding base members with new, and understanding the rules of inheritance in .NET.
Why It Matters
Inheritance is a fundamental pillar of object-oriented programming. It enables code reuse, establishes type hierarchies, and polymorphic behavior. Proper use of virtual and override creates extensible frameworks. Understanding when to use sealed prevents fragile base class syndrome. Many .NET frameworks, including ASP.NET Core, Windows Forms, and WPF, rely on inheritance hierarchies.
Real-World Use
ASP.NET Core middleware, controllers, and authorization handlers use inheritance for extensibility. Entity Framework Core's DbContext uses virtual methods for configuration. Windows Forms and WPF have deep inheritance hierarchies. Custom exception types inherit from Exception. Stream classes inherit from the abstract Stream base class.
Learning Path
graph LR
A["11: Encapsulation"] --> B["12: Inheritance"]
B --> C["13: Polymorphism"]
C --> D["14: Interfaces"]
D --> E["15: Records"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Basic Inheritance
A derived class inherits all non-private members from its base class:
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public void Eat() => Console.WriteLine($"{Name} is eating");
public void Sleep() => Console.WriteLine($"{Name} is sleeping");
}
public class Dog : Animal
{
public void Bark() => Console.WriteLine($"{Name} says Woof!");
}
// Usage
var dog = new Dog { Name = "Rex", Age = 3 };
dog.Eat(); // Inherited from Animal
dog.Bark(); // Defined in Dog
Expected output:
Rex is eating
Rex says Woof!
Virtual and Override
The virtual keyword allows a method to be overridden in a derived class:
public class Shape
{
public string Name { get; set; }
// Virtual method: can be overridden
public virtual double CalculateArea() => 0;
// Virtual method with default implementation
public virtual void Display()
{
Console.WriteLine($"Shape: {Name}");
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public Circle()
{
Name = "Circle";
}
public override double CalculateArea() => Math.PI * Radius * Radius;
public override void Display()
{
base.Display(); // Call base implementation
Console.WriteLine($" Radius: {Radius}");
Console.WriteLine($" Area: {CalculateArea():F2}");
}
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle()
{
Name = "Rectangle";
}
public override double CalculateArea() => Width * Height;
public override void Display()
{
base.Display();
Console.WriteLine($" Dimensions: {Width} x {Height}");
Console.WriteLine($" Area: {CalculateArea():F2}");
}
}
Base Constructor Calls
Derived classes must call a base constructor using base(...):
public class Person
{
public string Name { get; }
public DateTime BirthDate { get; }
public Person(string name, DateTime birthDate)
{
Name = name;
BirthDate = birthDate;
}
public Person(string name) : this(name, DateTime.UtcNow)
{
}
}
public class Employee : Person
{
public string EmployeeId { get; }
public decimal Salary { get; }
public Employee(string name, string employeeId, decimal salary)
: base(name) // Calls Person(string name)
{
EmployeeId = employeeId;
Salary = salary;
}
public Employee(string name, DateTime birthDate, string employeeId, decimal salary)
: base(name, birthDate) // Calls Person(string, DateTime)
{
EmployeeId = employeeId;
Salary = salary;
}
}
var emp = new Employee("Alice", "EMP001", 75000m);
Console.WriteLine($"{emp.Name} (ID: {emp.EmployeeId})");
The base Keyword
Calls the base class implementation:
public class Logger
{
public virtual void Log(string message)
{
Console.WriteLine($"[{DateTime.UtcNow:O}] {message}");
}
}
public class FileLogger : Logger
{
private string _filePath;
public FileLogger(string filePath)
{
_filePath = filePath;
}
public override void Log(string message)
{
base.Log(message); // Call base to write to console
File.AppendAllText(_filePath, $"[{DateTime.UtcNow:O}] {message}\n");
}
}
The Sealed Keyword
Prevents further inheritance:
public class BaseClass
{
public virtual void Method() { }
}
public class Derived : BaseClass
{
public sealed override void Method() // Cannot be overridden further
{
base.Method();
}
}
// Compilation error: cannot derive from sealed class
// public class FurtherDerived : Derived { }
Sealed Classes
public sealed class Configuration
{
public string ConnectionString { get; init; }
public int Timeout { get; init; }
}
// Compilation error: 'class' cannot derive from sealed type 'Configuration'
// public class ExtendedConfig : Configuration { }
The New Keyword (Member Hiding)
The new keyword hides a base class member intentionally:
public class Base
{
public void Display()
{
Console.WriteLine("Base Display");
}
}
public class Derived : Base
{
public new void Display() // Hides Base.Display, not override
{
Console.WriteLine("Derived Display");
}
}
// Warning: this is different from override!
Base b = new Derived();
b.Display(); // "Base Display" (no polymorphism!)
Derived d = new Derived();
d.Display(); // "Derived Display"
Use new when you intentionally want to hide a non-virtual member. This is rarely needed and should be avoided.
Abstract Classes and Methods
Abstract classes cannot be instantiated and may contain abstract members:
public abstract class Vehicle
{
public string Make { get; set; }
public string Model { get; set; }
// Abstract method: must be overridden
public abstract void StartEngine();
// Abstract property
public abstract int MaxSpeed { get; }
// Regular method with implementation
public void DisplayInfo()
{
Console.WriteLine($"{Make} {Model}, Max: {MaxSpeed} mph");
}
}
public class Car : Vehicle
{
public override int MaxSpeed => 150;
public override void StartEngine()
{
Console.WriteLine("Car engine started with key turn");
}
}
public class ElectricCar : Vehicle
{
public override int MaxSpeed => 120;
public override void StartEngine()
{
Console.WriteLine("Electric motor activated silently");
}
}
Inheritance and Polymorphism
List<Vehicle> vehicles = new()
{
new Car { Make = "Honda", Model = "Civic" },
new ElectricCar { Make = "Tesla", Model = "Model 3" }
};
foreach (var v in vehicles)
{
v.StartEngine(); // Polymorphic call
v.DisplayInfo();
Console.WriteLine();
}
Expected output:
Car engine started with key turn
Honda Civic, Max: 150 mph
Electric motor activated silently
Tesla Model 3, Max: 120 mph
Common Mistakes
Mistake 1: Forgetting to Call base Constructor
If the base class lacks a parameterless constructor, derived classes must explicitly call base(...). The compiler error message is clear: "'BaseClass' does not contain a constructor that takes 0 arguments."
Mistake 2: Using new Instead of override
Creating a method with new in a derived class does not enable polymorphism. The base class method is called when using a base type reference. This is almost always unintentional.
Mistake 3: Overriding Without virtual
You cannot override a method that is not marked virtual, abstract, or override in the base class. The compiler treats it as a new method and issues a warning.
Mistake 4: Deep Inheritance Hierarchies
More than 3-4 levels of inheritance is hard to understand and maintain. Prefer Composition Over Inheritance for complex scenarios.
Mistake 5: Calling Virtual Methods in Base Constructors
If a base constructor calls a virtual method, the derived class's override runs before the derived constructor executes. This can lead to bugs if the override depends on derived state.
Mistake 6: Sealing Everything Prematurely
While sealing can protect against misuse, it also prevents extensibility. Seal only when you have a specific reason, such as security or design considerations.
Practice Questions
- What is the difference between
virtualandabstractmethods? - Why must derived classes call a base constructor?
- What happens if you use
newinstead ofoverridein a derived class? - How does the
sealedkeyword work with methods versus classes? - Create a base class
Employeeand derived classesManagerandDeveloperwith appropriate virtual methods.
Challenge
Design a class hierarchy for a payroll system: an abstract Employee base class with CalculatePay() as abstract, then SalariedEmployee, HourlyEmployee, and CommissionEmployee derived classes. Each should override CalculatePay with appropriate logic.
FAQ
Mini Project
Create a media library hierarchy:
public abstract class MediaItem
{
public string Title { get; set; }
public string Creator { get; set; }
public int Year { get; set; }
public abstract string GetDescription();
public virtual void Play()
{
Console.WriteLine($"Playing: {Title}");
}
}
public class Book : MediaItem
{
public int Pages { get; set; }
public string Isbn { get; set; }
public override string GetDescription()
{
return $"Book: '{Title}' by {Creator}, {Pages} pages (ISBN: {Isbn})";
}
public sealed override void Play()
{
Console.WriteLine($"Reading: {Title}");
}
}
public class Movie : MediaItem
{
public int DurationMinutes { get; set; }
public string Director { get; set; }
public override string GetDescription()
{
return $"Movie: '{Title}' directed by {Director}, {DurationMinutes} min";
}
public override void Play()
{
base.Play();
Console.WriteLine($" Duration: {DurationMinutes} minutes");
}
}
public class Album : MediaItem
{
public int TrackCount { get; set; }
public override string GetDescription()
{
return $"Album: '{Title}' by {Creator}, {TrackCount} tracks";
}
}
var items = new List<MediaItem>
{
new Book { Title = "Clean Code", Creator = "Robert Martin", Year = 2008, Pages = 464, Isbn = "978-0132350884" },
new Movie { Title = "Inception", Creator = "Christopher Nolan", Year = 2010, DurationMinutes = 148, Director = "Christopher Nolan" },
new Album { Title = "Thriller", Creator = "Michael Jackson", Year = 1982, TrackCount = 9 }
};
foreach (var item in items)
{
Console.WriteLine(item.GetDescription());
item.Play();
Console.WriteLine();
}
Expected output:
Book: 'Clean Code' by Robert Martin, 464 pages (ISBN: 978-0132350884)
Reading: Clean Code
Movie: 'Inception' directed by Christopher Nolan, 148 min
Playing: Inception
Duration: 148 minutes
Album: 'Thriller' by Michael Jackson, 9 tracks
Playing: Thriller
What's Next
You have mastered inheritance in C# including virtual, override, sealed, and abstract classes. The next lesson covers polymorphism: method overloading, overriding, and abstract classes in more depth.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro