Payroll code is where I see inheritance used well and abused most. Inheritance in C# lets a derived class reuse and extend a base class: write class Employee : Person, and every Employee gets the members of Person plus its own.
In this tutorial, I’ll map out how inheritance in C# really behaves on .NET 10: what gets inherited, constructor order, override versus new, multilevel classes, sealed types, records, and when to skip it.
My running example is a small US HR app with Person, Employee and Manager classes and salaries in US dollars.
What Is Inheritance in C#?
The class you inherit from is the base (parent) class, and the new one is the derived (child) class. It’s one of the pillars in my OOP concepts in C# guide, next to encapsulation and polymorphism.
The syntax is a colon after the class name. Here, Employee calls Greet() even though only Person declares it:
var emp = new Employee { Name = "Jenna Moore", City = "Seattle", Title = "Payroll Analyst" };
Console.WriteLine(emp.Greet()); // declared in Person
Console.WriteLine(emp.Badge()); // declared in Employee
Console.WriteLine(emp is Person); // an Employee is a Person
Console.WriteLine(emp.ToString()); // inherited from System.Object
class Person
{
public string Name { get; set; } = "";
public string City { get; set; } = "";
public string Greet() => $"Hi, I'm {Name} from {City}.";
}
class Employee : Person
{
public string Title { get; set; } = "";
public string Badge() => $"{Name} | {Title}";
}
Output:
Hi, I'm Jenna Moore from Seattle.
Jenna Moore | Payroll Analyst
True
Employee
That last line prints Employee because every class implicitly inherits from System.Object, which gives it ToString in C#, Equals and GetHashCode. The is check returns True because an Employee really is a Person.
Inheritance Rules in C#: What a Derived Class Gets (and Doesn’t)
A derived class gets every member of its base except the ones in the first row below. Whether it can see an inherited member depends on its access modifier:
| Base class member | Inherited? | Visible in the derived class? |
|---|---|---|
| Instance constructors, static constructors, finalizers | No | Call base constructors with base(...) |
public members | Yes | Yes, and part of the derived class’s public surface |
protected members | Yes | Yes, but not to outside code |
internal members | Yes | Only from the same assembly |
private members | Yes (the data is there) | No, unless the derived class is nested inside the base |
Here’s what happens when a derived class reaches for a private field. The protected property compiles; the private field stops the build:
var emp = new Employee();
Console.WriteLine(emp.ReadBand());
class Person
{
private decimal _bonusPool = 25_000m;
protected string PayBand { get; } = "B2";
}
class Employee : Person
{
public string ReadBand() => PayBand; // protected: fine
public decimal ReadPool() => _bonusPool; // private: CS0122
}
Build output:
C:\DotNetDemo\InheritanceDemo\PrivateMembers.cs(13,34): error CS0122: 'Person._bonusPool' is inaccessible due to its protection level
The build failed. Fix the build errors and run again.
That’s encapsulation in C# doing its job. If a derived class truly needs a value, expose it as protected, ideally as a read-only property rather than a field.
Inheritance and Constructors: base() Chaining and Call Order
Constructors aren’t inherited, so each class writes its own and passes values up with : base(...). I added a WriteLine to each constructor so you can watch the order on a three-level chain:
var boss = new Manager("Ryan Carter", 98_000m);
Console.WriteLine($"Created {boss.Name}");
class Person
{
public string Name { get; }
public Person(string name)
{ Name = name; Console.WriteLine("1. Person"); }
}
class Employee : Person
{
public decimal Salary { get; }
public Employee(string name, decimal salary) : base(name)
{ Salary = salary; Console.WriteLine("2. Employee"); }
}
class Manager : Employee
{
public Manager(string name, decimal salary)
: base(name, salary) => Console.WriteLine("3. Manager");
}
Output:
1. Person
2. Employee
3. Manager
Created Ryan Carter

The base constructor body always finishes before the derived one starts, so Person prints first even though I called new Manager(...). My guide to the constructor in C# covers this(...) chaining too.
Field note: If you leave out : base(...), the compiler calls the base’s parameterless constructor. When the base doesn’t have one, you get error CS7036, “There is no argument given that corresponds to the required parameter.”
virtual, override and new: Overriding vs Method Hiding in Inheritance
This is the part of inheritance that bites people in code reviews. override replaces a virtual method for every caller, while new only hides the base method when you call through the derived type:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
Employee e = new Manager { Name = "Zoe Ng", Pay = 112_500m };
Console.WriteLine(e.Describe()); // override wins
Console.WriteLine(e.PayLabel()); // new: base runs
Console.WriteLine(((Manager)e).PayLabel()); // new version
class Employee
{
public string Name { get; set; } = "";
public decimal Pay { get; set; }
public virtual string Describe() => $"{Name}, employee";
public string PayLabel() => $"Salary {Pay:C0}";
}
class Manager : Employee
{
public override string Describe() =>
base.Describe() + " (manager)";
public new string PayLabel() => $"Salary {Pay:C0} + bonus";
}
Output:
Zoe Ng, employee (manager)
Salary $112,500
Salary $112,500 + bonus

The variable e is typed Employee but holds a Manager. Describe() is virtual, so the runtime picks the Manager version; PayLabel() isn’t, so the compile-time type wins and you lose the bonus text.
Inside an override, base.Describe() reuses the parent’s logic. I go deeper in method overriding in C#; don’t confuse it with method overloading in C#, which is same name, different parameters.
Field note: Hiding a method without new still compiles, with warning CS0108 (or CS0114 when the base method is virtual). Treat that warning as “you probably meant override.”
Types of Inheritance in C#: Single, Multilevel and Hierarchical
C# allows one direct base class per class, but you can arrange classes in several shapes:
| Type | Shape | Allowed for classes? |
|---|---|---|
| Single | Employee : Person | Yes |
| Multilevel | Manager : Employee : Person | Yes |
| Hierarchical | Employee and Contractor both : Person | Yes |
| Multiple | Manager : Employee, Contractor | No, use interfaces |
| Hybrid | A mix of the above | Only through interfaces |
Multilevel Inheritance in C#: Person -> Employee -> Manager
Inheritance is transitive, so Manager gets members from Employee and from Person. Here’s a C# program to implement multilevel inheritance:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
var chloe = new Manager
{ Name = "Chloe Brady", Salary = 104_000m, Bonus = 12_000m };
Console.WriteLine(chloe.Name); // Person
Console.WriteLine($"Salary: {chloe.Salary:C0}"); // Employee
Console.WriteLine($"Total: {chloe.TotalPay():C0}"); // Manager
class Person { public string Name { get; set; } = ""; }
class Employee : Person
{
public decimal Salary { get; set; }
public virtual decimal TotalPay() => Salary;
}
class Manager : Employee
{
public decimal Bonus { get; set; }
public override decimal TotalPay() => base.TotalPay() + Bonus;
}
Output:
Chloe Brady
Salary: $104,000
Total: $116,000

TotalPay() calls base.TotalPay() and adds the bonus, so a change in Employee flows down automatically. I set en-US explicitly, because :C0 follows the machine’s culture and this PC isn’t set to US English.
Keep chains shallow. Past three levels, I find a change near the top tends to break something two classes down that nobody remembers.
Why Doesn’t C# Support Multiple Inheritance of Classes?
Two base classes could both define CalculatePay() with different state behind them, and the compiler would have to guess which one you meant. C# avoids that with one base class plus any number of interfaces.
I walk through the interface workaround, default interface methods included, in multiple inheritance in C#. Structs can’t inherit at all, though a structure in C# can implement interfaces.
Abstract Classes vs Interfaces in an Inheritance Hierarchy
Both define a contract, but they answer different questions:
| Abstract class | Interface | |
|---|---|---|
| Relationship | “is a” (Manager is an Employee) | “can do” (can be paid, can be compared) |
| How many per class | One | Many |
| Instance fields | Yes | No |
| Constructors | Yes | No |
I use an abstract base when classes share real code and data, like Person further down. The details are in abstract class in C# and interface in C#.
Sealed Classes and Sealed Methods: Stopping Inheritance in C#
Mark a class sealed when nobody should derive from it. Trying anyway is a compile error, not a runtime surprise:
var i = new Intern { Name = "Trevor Lyons" };
Console.WriteLine(i.Describe());
class Employee
{
public string Name { get; set; } = "";
public virtual string Describe() => $"{Name}, employee";
}
sealed class Intern : Employee
{
public override string Describe() => $"{Name}, summer intern";
}
class SummerIntern : Intern { } // CS0509
Build output:
C:\DotNetDemo\InheritanceDemo\SealedClass.cs(15,22): error CS0509: 'SummerIntern': cannot derive from sealed type 'Intern'
The build failed. Fix the build errors and run again.
You can also seal a single override, public sealed override string Describe(), so classes further down can’t override it again. string is sealed, which is why you can’t inherit from it.
How Do You Check Derived Types in Inheritance with is, as and Patterns?
Hierarchical inheritance shows up the moment you store mixed types in one list. Here Employee and Contractor both derive from an abstract Person record, and a switch expression sorts them out:
Person[] staff = [new Employee("Jenna Moore", 76_000m),
new Contractor("Ethan Rodriguez", 85m),
new Employee("Dwayne Foster", 91_500m)];
foreach (Person p in staff)
{
string kind = p switch
{
Employee { Salary: > 90_000m } => "senior employee",
Employee => "employee",
Contractor c => $"contractor at {c.Rate}/hour",
_ => "person"
};
Console.WriteLine($"{p.Name}: {kind}");
}
var asEmp = staff[1] as Employee;
Console.WriteLine($"as Employee: {asEmp?.Name ?? "null"}");
abstract record Person(string Name);
record Employee(string Name, decimal Salary) : Person(Name);
record Contractor(string Name, decimal Rate) : Person(Name);
Output:
Jenna Moore: employee
Ethan Rodriguez: contractor at 85/hour
Dwayne Foster: senior employee
as Employee: null

Order matters: the { Salary: > 90_000m } property pattern has to come before the plain Employee case, or it never matches. The switch case in C# guide covers more patterns.
as returns null instead of throwing, while a direct cast throws InvalidCastException. My notes on type casting in C# compare the two.
Record Inheritance in C#
A record can inherit from another record, but not from a class, and a class can’t inherit from a record. Positional syntax passes values to the base record:
var p = new Person("Ryan Carter", "Ohio");
var e = new Employee("Ryan Carter", "Ohio", 88_000m);
Console.WriteLine(e);
Console.WriteLine(p == (Person)e); // run-time types differ
Console.WriteLine(e with { State = "Utah" });
record Person(string Name, string State);
record Employee(string Name, string State, decimal Salary) : Person(Name, State);
Output:
Employee { Name = Ryan Carter, State = Ohio, Salary = 88000 }
False
Employee { Name = Ryan Carter, State = Utah, Salary = 88000 }
Equality includes the run-time type, so a Person and an Employee with the same name and state aren’t equal. The generated ToString and the with expression both know about the derived Salary.
Composition over Inheritance: When Not to Use Inheritance in C#
My rule: inherit only for a true “is a” relationship. If you’re tempted to write HourlyEmployee and SalariedEmployee, the difference is a behavior, not a kind of person.
Give the employee a pay policy instead. You avoid a subclass for every pay rule:
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
var hourly = new Employee("Kelly Hernandez", new HourlyPay(42m, 160));
var salaried = new Employee("Rebecca Hughes", new SalaryPay(96_000m));
Console.WriteLine($"{hourly.Name}: {hourly.Pay.Monthly():C0}/month");
Console.WriteLine($"{salaried.Name}: {salaried.Pay.Monthly():C0}/month");
interface IPayPolicy { decimal Monthly(); }
record HourlyPay(decimal Rate, int Hours) : IPayPolicy { public decimal Monthly() => Rate * Hours; }
record SalaryPay(decimal Annual) : IPayPolicy { public decimal Monthly() => Annual / 12; }
record Employee(string Name, IPayPolicy Pay);
Output:
Kelly Hernandez: $6,720/month
Rebecca Hughes: $8,000/month
Each Employee has a pay policy rather than being one. Adding a commission plan is a new record, not a new branch in the class tree.
Inheritance in C#: Which Tool Should You Reach For?
| You want to… | Use |
|---|---|
| Share code and data across related types | A base class (abstract if it shouldn’t be created) |
| Let a derived class change behavior | virtual + override |
| Stop further derivation | sealed |
| Give unrelated types a common ability | An interface |
| Vary one behavior at run time | Composition |
| Immutable data with a parent shape | Record inheritance |
Want to round out your OOP toolkit? These pair well with this guide:
- Abstraction in C# with real examples
- Split a class across files with partial classes
- Dispose and Finalize in C#
- C# interview questions for experienced developers
Frequently Asked Questions
What is inheritance in C# with an example?
Inheritance lets one class reuse another’s members. With class Employee : Person, an Employee object can call methods and properties declared in Person and add its own.
Are constructors inherited in C#?
No. Instance constructors, static constructors and finalizers aren’t inherited. A derived constructor calls a base constructor with : base(...), and the base constructor runs first.
What is the difference between override and new in C#?
override replaces a virtual method for every caller, including calls through a base-class variable. new only hides the base method, so a base-class variable still runs the base version.
Does C# support multiple inheritance?
Not for classes. A class has one direct base class but can implement any number of interfaces, which covers most multiple-inheritance needs.
What is multilevel inheritance in C#?
A chain where a class derives from a class that is itself derived, such as Manager : Employee : Person. The inheritance tutorial on Microsoft Learn calls this transitive inheritance.
Can a record inherit from a class in C#?
No. A record class can inherit only from another record class, and record structs can’t inherit at all. See C# record types on Microsoft Learn.
Bijay Kumar is a renowned software engineer, accomplished author, and distinguished Microsoft Most Valuable Professional (MVP) specializing in SharePoint. With a rich professional background spanning over 15 years, Bijay has established himself as an authority in the field of information technology. He possesses unparalleled expertise in multiple programming languages and technologies such as ASP.NET, ASP.NET MVC, C#.NET, and SharePoint, which has enabled him to develop innovative and cutting-edge solutions for clients across the globe. Read more…