Inheritance
One class can build on another: what gets inherited, how base(...) runs the base constructor, and why C# allows only one base class.
class Derived : Base { public Derived(int x) : base(x) { } }Inheritance lets one class build on another. The class you start from is the base class. The class built on it is the derived class. The derived class gets the base class members it is allowed to see, without copying any code, and can add members of its own.
It exists so that shared data and behaviour live in one place. Change the base class and every derived class changes with it.
How it works
Write a colon and the base class name after the derived class name. class Car : Vehicle reads as "a Car is a Vehicle".
Constructors are not inherited. If the base class constructor takes arguments, the derived constructor has to supply them with base(...). The base constructor runs first, then the body of the derived constructor.
One base class only
A class may name exactly one base class. There is no way to inherit from two. Interfaces, covered later, are how one type takes on several roles. A class that names no base class derives from object, so object sits above every type in C#.
What trips people up
A private base member is present in the derived object but the derived class cannot touch it. Mark it protected if children need it. Also, inheritance is not a way to share a few handy methods. If "a Car is a Vehicle" is false, hold the other object in a field instead.
Example
Vehicle bike = new Vehicle("Bike", 2);
Car car = new Car("Saloon", 4, "petrol");
// Describe is declared once, on Vehicle; Car gets it without copying it
Console.WriteLine(bike.Describe());
Console.WriteLine(car.Describe());
Console.WriteLine(car.Fuel);
class Vehicle
{
public Vehicle(string name, int wheels)
{
Name = name;
Wheels = wheels;
}
public string Name { get; }
public int Wheels { get; }
// => expr is shorthand for { return expr; } - the same method
public string Describe() => $"{Name} has {Wheels} wheels";
}
class Car : Vehicle
{
// base(...) runs Vehicle's constructor before this body
public Car(string name, int wheels, string fuel) : base(name, wheels)
{
Fuel = fuel;
}
public string Fuel { get; }
}
// Output:
// Bike has 2 wheels
// Saloon has 4 wheels
// petrolWhen to use it
- Share Name and Id properties across Customer, Supplier and Employee without writing them three times.
- Add a Discount property to a Product subclass used only in the sale catalogue.
- Extend a class from a library you did not write with one extra method, instead of copying its source.
More examples
Primary constructor shorthand, next to the longhand
A primary constructor is a shorter spelling of the same constructor. The parameter list sits on the class declaration and the base arguments sit in the base type list. Reach for it when the constructor only stores its arguments; use the longhand when the constructor has to validate something or do real work, because a primary constructor has no body of its own.
// Longhand: a constructor that forwards two of its arguments to the base
class CarLong : Vehicle
{
public CarLong(string name, int wheels, string fuel) : base(name, wheels)
{
Fuel = fuel;
}
public string Fuel { get; }
}
// Shorthand, added in C# 12: the parameters go after the class name and
// the base arguments go after the base class name. Same generated class.
class CarShort(string name, int wheels, string fuel) : Vehicle(name, wheels)
{
public string Fuel { get; } = fuel;
}
class Vehicle
{
public Vehicle(string name, int wheels)
{
Name = name;
Wheels = wheels;
}
public string Name { get; }
public int Wheels { get; }
}protected is for children, private is not
Shows the one access decision inheritance forces you to make. Start every field private. Widen it to protected only when a derived class genuinely needs to read or write it, because protected is part of your public contract to anyone who inherits from the class.
class Account
{
// protected: this class and any derived class, and nothing else
protected decimal _balance;
// private: only this class, even though derived objects contain it
private string _auditToken = "none";
public string AuditToken => _auditToken;
public void Deposit(decimal amount) => _balance += amount;
}
class SavingsAccount : Account
{
public decimal AddInterest(decimal rate)
{
_balance += _balance * rate; // allowed: _balance is protected
// _auditToken = "changed"; // error: inaccessible here
return _balance;
}
}Constructors run from the top down
Proves the order: the most distant base constructor finishes first and the most derived one finishes last. This matters when a base constructor reads state the derived constructor has not set yet, which is the cause of a whole family of null-reference bugs at startup.
new Child();
class Grandparent
{
public Grandparent() => Console.WriteLine("Grandparent");
}
class Parent : Grandparent
{
// no base(...) written, so base() is called implicitly
public Parent() => Console.WriteLine("Parent");
}
class Child : Parent
{
public Child() => Console.WriteLine("Child");
}
// Output:
// Grandparent
// Parent
// Child
Discussion