Skip to main content

πŸ“ 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 protected members and call a base constructor with base(...)
  • Override a base method using virtual and override
  • 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.

graph TD A["Animal
(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:

ModifierAccessible from…
publicAnywhere
privateOnly inside the same class (not even derived classes)
protectedInside 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 SavingsAccount is a BankAccount).
  • 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 Car has an Engine β€” it isn't a kind of engine. Model that by giving Car an Engine property (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:

  1. Create a new project called Vehicles.
  2. Make a base class Vehicle with a Brand property (set via a constructor) and a virtual method Describe() that prints the brand.
  3. Make Car : Vehicle and Motorcycle : Vehicle. Each should pass the brand up with base(...) and override Describe() to print something specific (e.g. number of wheels).
  4. 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.
  • protected members are visible to the class and its derived classes; private ones 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 via base.Method().

πŸ“š Additional Resources

πŸš€ 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.