π Lesson 5.1: Inheritance
You know how to build a class. Now you'll learn how to build a class from another class β reusing what already exists and specializing it. This is inheritance, one of the pillars of object-oriented programming.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain what inheritance is and the "is-a" relationship
- Create a derived class from a base class with the
:syntax - Use
protectedmembers and call a base constructor withbase(...) - Override a base method using
virtualandoverride - Decide when inheritance is (and isn't) the right tool
Estimated Time: 60 minutes
Project: Model a small family of related classes and specialize their behavior.
In This Lesson
What Is Inheritance?
Inheritance lets one class build on another. A new class can take everything an existing class has β its properties and methods β and then add or change things. This avoids duplicating code and models real-world relationships.
π Definition
Inheritance: a mechanism where a derived class (child) automatically gains the members of a base class (parent), and can extend or customize them.
The test for inheritance is the "is-a" relationship. A Dog is an Animal. A SavingsAccount is a BankAccount. A Student is a Person. When one thing is a more specific kind of another, inheritance fits.
(base class)
Name, Eat()"] --> B["Dog
Bark()"] A --> C["Cat
Meow()"] style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px style B fill:#e8f5e9,stroke:#4CAF50,stroke-width:2px style C fill:#e8f5e9,stroke:#4CAF50,stroke-width:2px
Both Dog and Cat automatically get Name and Eat() from Animal β they only add what makes them special.
Base and Derived Classes
Start with a base class β an ordinary class like the ones from Lesson 4.2:
class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
To make a class inherit from it, put a colon : and the base class name after the derived class name:
class Dog : Animal // "Dog is an Animal"
{
public void Bark()
{
Console.WriteLine($"{Name} says: Woof!"); // Name comes from Animal
}
}
Now a Dog object has both the inherited members and its own:
Dog rex = new Dog();
rex.Name = "Rex"; // inherited property
rex.Eat(); // inherited method β Rex is eating.
rex.Bark(); // Dog's own method β Rex says: Woof!
Output:
Rex is eating.
Rex says: Woof!
π‘ Terminology
The class being inherited from is the base class (also "parent" or "superclass"). The class doing the inheriting is the derived class (also "child" or "subclass"). In C#, a class can derive from only one base class.
Access: public, private, protected
Inheritance adds a third useful access level. Here's how the three you'll use compare:
| Modifier | Accessible from⦠|
|---|---|
public | Anywhere |
private | Only inside the same class (not even derived classes) |
protected | Inside the class and its derived classes |
Use protected when a base class has something its children need to touch, but the outside world shouldn't:
class Animal
{
public string Name { get; set; }
protected int energy = 100; // children can use it; outsiders can't
public void Eat()
{
energy += 10;
Console.WriteLine($"{Name} is eating. Energy: {energy}");
}
}
class Dog : Animal
{
public void Run()
{
energy -= 20; // OK β 'energy' is protected
Console.WriteLine($"{Name} runs. Energy: {energy}");
}
}
β οΈ private members are not accessible in derived classes
If energy above were private, the Dog.Run method could not use it β a compile error. The derived object still has that data internally, but only the base class's own code can touch it directly. Choose protected when children legitimately need access.
Constructors and base
When a base class has a constructor that takes arguments, the derived class must pass those along. You do this with the base(...) call after the derived constructor's parameter list:
class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
}
class Dog : Animal
{
public string Breed { get; set; }
// Pass 'name' up to the Animal constructor with base(name)
public Dog(string name, string breed) : base(name)
{
Breed = breed;
}
}
Dog rex = new Dog("Rex", "Labrador");
Console.WriteLine($"{rex.Name} is a {rex.Breed}."); // Rex is a Labrador.
π‘ What base(name) does
It runs the base class's constructor first (setting up the inherited part of the object), then the derived constructor's body runs. Order matters: the base is always fully initialized before the derived class adds its own setup.
Overriding Methods
Sometimes a derived class needs to change an inherited method, not just add new ones. That's overriding. It takes two keywords working together:
- Mark the base method
virtualβ "this method may be overridden." - Mark the derived method
overrideβ "this replaces the base version."
class Animal
{
public string Name { get; set; }
public virtual void Speak() // virtual: can be overridden
{
Console.WriteLine($"{Name} makes a sound.");
}
}
class Dog : Animal
{
public override void Speak() // override: replaces the base version
{
Console.WriteLine($"{Name} says: Woof!");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine($"{Name} says: Meow!");
}
}
Animal generic = new Animal { Name = "Thing" };
Dog rex = new Dog { Name = "Rex" };
Cat felix = new Cat { Name = "Felix" };
generic.Speak(); // Thing makes a sound.
rex.Speak(); // Rex says: Woof!
felix.Speak(); // Felix says: Meow!
Extending, not just replacing: base.Method()
An override can also reuse the base version and add to it, by calling base.MethodName():
class Puppy : Dog
{
public override void Speak()
{
base.Speak(); // run Dog's version first...
Console.WriteLine("(then wags tail)"); // ...then add to it
}
}
β virtual + override, always together
You can only override a method the base class marked virtual (or abstract, covered in Lesson 5.3). This is a deliberate, visible contract β the base class decides what's open to change. This ability for the same call to behave differently per type is the foundation of polymorphism, our topic in Lesson 5.3.
When to Use Inheritance
Inheritance is powerful, but it's easy to overuse. A quick guide:
β Reach for inheritance whenβ¦
- There's a genuine "is-a" relationship (a
SavingsAccountis aBankAccount). - Derived types share real, common behavior that lives naturally in a base class.
β οΈ Avoid inheritance whenβ¦
- The relationship is really "has-a", not "is-a". A
Carhas anEngineβ it isn't a kind of engine. Model that by givingCaranEngineproperty (this is called composition), not by inheriting. - You're inheriting just to grab a method or two you find convenient. That leads to fragile, confusing hierarchies.
π‘ Rule of thumb: Say the relationship out loud. "A Dog is an Animal" β points to inheritance. "A Car has an Engine" β points to composition (a property). If "is-a" sounds wrong, don't inherit.
Exercise & Quiz
ποΈ Exercise: A Vehicle Hierarchy
Objective: Build a base class and two derived classes that override behavior.
Instructions:
- Create a new project called
Vehicles. - Make a base class
Vehiclewith aBrandproperty (set via a constructor) and avirtualmethodDescribe()that prints the brand. - Make
Car : VehicleandMotorcycle : Vehicle. Each should pass the brand up withbase(...)andoverrideDescribe()to print something specific (e.g. number of wheels). - Create one of each, put them to work, and call
Describe()on each.
Starter Code:
Car car = new Car("Toyota");
Motorcycle bike = new Motorcycle("Harley");
car.Describe();
bike.Describe();
class Vehicle
{
public string Brand { get; set; }
public Vehicle(string brand) { Brand = brand; }
public virtual void Describe()
{
Console.WriteLine($"This is a {Brand} vehicle.");
}
}
// TODO: Car : Vehicle (override Describe, mention 4 wheels)
// TODO: Motorcycle : Vehicle (override Describe, mention 2 wheels)
π‘ Hint
A derived class header looks like class Car : Vehicle, and its constructor forwards the brand: public Car(string brand) : base(brand) { }. Then write public override void Describe() with its own message. You can call base.Describe(); inside if you want to reuse the base text.
β Solution
Car car = new Car("Toyota");
Motorcycle bike = new Motorcycle("Harley");
car.Describe();
bike.Describe();
class Vehicle
{
public string Brand { get; set; }
public Vehicle(string brand) { Brand = brand; }
public virtual void Describe()
{
Console.WriteLine($"This is a {Brand} vehicle.");
}
}
class Car : Vehicle
{
public Car(string brand) : base(brand) { }
public override void Describe()
{
Console.WriteLine($"{Brand} car β 4 wheels, doors, and a trunk.");
}
}
class Motorcycle : Vehicle
{
public Motorcycle(string brand) : base(brand) { }
public override void Describe()
{
Console.WriteLine($"{Brand} motorcycle β 2 wheels and no roof!");
}
}
Output:
Toyota car β 4 wheels, doors, and a trunk.
Harley motorcycle β 2 wheels and no roof!
π― Quick Quiz
Question 1: Which relationship is a good fit for inheritance?
Question 2: Which two keywords let a derived class replace a base method?
Question 3: A protected member of a base class can be accessedβ¦
Summary
π Key Takeaways
- Inheritance lets a derived class reuse and extend a base class; use it for genuine "is-a" relationships.
- Declare it with
class Derived : Base. A C# class can inherit from only one base class. protectedmembers are visible to the class and its derived classes;privateones are not inherited-accessible.- Forward constructor arguments to the base with
: base(...); the base is initialized first. - Override behavior with
virtual(base) +override(derived); reuse the base version viabase.Method().
π Additional Resources
- Inheritance β Microsoft Docs
- The
basekeyword β reference - Inheritance in C# β Programming Guide
π What's Next?
Inheritance links classes by "is-a." But there's another way to share capabilities across unrelated classes: interfaces. In Lesson 5.2: Interfaces, you'll define contracts that any class can promise to fulfill.
π Inheritance unlocked!
You can now build families of related classes without repeating yourself. Next: interfaces.