Base and Derived Classes - Structuring Inheritance in C#

Vaibhav • September 10, 2025

In the previous article, we introduced the concept of inheritance - how classes can reuse and extend behavior by forming “is-a” relationships. Now, we take a closer look at the two key roles in inheritance: base classes and derived classes. Understanding how to design and use these classes effectively is essential for building clean, extensible object-oriented systems in C#.

A base class defines common functionality that can be shared across multiple types. A derived class inherits from the base class and can add, override, or specialize behavior. This article explores how to structure base and derived classes, how access modifiers affect inheritance, and how to design class hierarchies that are flexible and maintainable.

Defining a Base Class

A base class is a general-purpose class that provides shared members - fields, properties, and methods - for other classes to inherit. It serves as the foundation of a class hierarchy.

public class Animal
{
    public string Name { get; set; }

    public void Eat()
    {
        Console.WriteLine($"{Name} is eating.");
    }
}

This Animal class defines a Name property and an Eat() method. Any class that inherits from Animal will automatically have access to these members.

Creating a Derived Class

A derived class extends a base class using the : syntax. It inherits all accessible members from the base class and can add new members or override existing ones.

public class Dog : Animal
{
    public void Bark()
    {
        Console.WriteLine($"{Name} says woof!");
    }
}

The Dog class inherits the Name property and Eat() method from Animal. It also adds a new method, Bark(), specific to dogs.

Dog d = new Dog();
d.Name = "Buddy";
d.Eat();   // Buddy is eating.
d.Bark();  // Buddy says woof!

This example shows how a derived class can use both inherited and new behavior seamlessly.

Access Modifiers and Inheritance

The accessibility of base class members affects what the derived class can use:

  • public members are accessible everywhere, including derived classes.
  • protected members are accessible only within the base class and derived classes.
  • private members are accessible only within the base class itself.
public class Animal
{
    protected string Species;

    public void SetSpecies(string name)
    {
        Species = name;
    }
}

public class Cat : Animal
{
    public void PrintSpecies()
    {
        Console.WriteLine($"Species: {Species}");
    }
}

The Species field is protected, so it can be accessed by Cat, but not by external code. This allows the base class to expose internal data only to trusted subclasses.

Use protected when you want to share implementation details with derived classes but keep them hidden from external consumers.

Using the base Keyword

The base keyword allows a derived class to refer to members of its base class explicitly. This is useful when you want to call a base class method that has been overridden or when passing arguments to a base constructor.

public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal sound");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        base.Speak(); // Calls base method
        Console.WriteLine("Dog says woof");
    }
}

The Dog class overrides Speak() but still calls the base implementation using base.Speak(). This allows you to extend behavior rather than replace it entirely.

Constructor Chaining with base

When a base class has a constructor that requires parameters, the derived class must call it using : base(...). This ensures that the base class is properly initialized.

public class Person
{
    public string Name { get; }

    public Person(string name)
    {
        Name = name;
    }
}

public class Student : Person
{
    public int Grade { get; }

    public Student(string name, int grade) : base(name)
    {
        Grade = grade;
    }
}

The Student constructor calls the Person constructor to initialize the Name property. This pattern is essential when the base class requires setup.

Designing Base Classes Thoughtfully

A base class should be designed to be reused and extended. Avoid exposing internal details unnecessarily. Use protected for members intended for inheritance, and document which methods are safe to override.

Keep base classes focused and cohesive. Don’t try to make them do everything - instead, provide a solid foundation that derived classes can build upon.

Design base classes with extensibility in mind. Avoid making assumptions about how derived classes will use them.

Avoiding Inheritance Pitfalls

Inheritance can lead to problems if misused:

  • Deep inheritance hierarchies are hard to understand and maintain.
  • Overriding methods without understanding base behavior can cause bugs.
  • Exposing too much in the base class can lead to tight coupling.

To avoid these issues:

  • Keep hierarchies shallow.
  • Use composition when appropriate.
  • Document base class behavior clearly.

Using Inheritance for Polymorphism

One of the main benefits of inheritance is polymorphism - the ability to treat derived objects as instances of the base class. This allows you to write flexible code that works with different types through a common interface.

public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal sound");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Meow!");
    }
}
List<Animal> animals = new List<Animal>
{
    new Dog(),
    new Cat()
};

foreach (var animal in animals)
{
    animal.Speak(); // Polymorphic call
}

Each object responds to Speak() in its own way, even though the loop treats them as Animal. This is the power of polymorphism.

Summary

Base and derived classes are the building blocks of inheritance in C#. A base class defines shared behavior, while derived classes extend and specialize that behavior. Together, they allow you to model real-world relationships, reuse code, and support polymorphism.

In this article, we explored how to define base and derived classes, how access modifiers affect inheritance, how to use the base keyword, and how to design class hierarchies thoughtfully. We also discussed constructor chaining, inheritance pitfalls, and the role of polymorphism.

As you continue building object-oriented systems, use inheritance to express clear relationships and promote reuse. In the next article, we’ll explore Method Overriding - how derived classes can customize behavior by redefining base class methods.