Skip to content

C# Inheritance — Base, Override, Virtual, Sealed, and New Keyword

DodaTech Updated 2026-06-28 9 min read

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

  1. What is the difference between virtual and abstract methods?
  2. Why must derived classes call a base constructor?
  3. What happens if you use new instead of override in a derived class?
  4. How does the sealed keyword work with methods versus classes?
  5. Create a base class Employee and derived classes Manager and Developer with 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

Can a class inherit from multiple classes?

No. C# does not support multiple inheritance of classes. A class can inherit from only one base class. Multiple inheritance is achieved through interfaces.

What is the difference between `override` and `new`?

override replaces the base class method polymorphically. When called through a base reference, the derived implementation runs. new hides the base method; the base implementation runs when called through a base reference.

Can I override a method marked as `sealed`?

No. The sealed modifier on a method prevents further overrides in derived classes. This is often used to lock down behavior at a specific level in the hierarchy.

Why would I use an abstract class instead of an interface?

Use abstract classes when you share implementation (fields, constructors, method bodies) across related classes. Use interfaces for contracts across unrelated types.

Can static methods be virtual?

No. Static methods cannot be virtual, abstract, or override. They belong to the type, not to instances, so polymorphism does not apply.

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