OOP Pillars – Inheritance in C#

Reading Time: 3 minutes

One of the most important principles of OOP is the DRY principle – Don’t Repeat Yourself. Inheritance is an aspect of OOP that facilitates code reuse and eliminates duplication. In this post I talk about the next of the OOP Pillars – Inheritance in C#. If you missed my previous post on Encapsulation, you can see it here.

The Basics

Inheritance comes in two ways: a “is-a” relationship and a “has-a” relationship. In this article I discuss the “is-a” type of inheritance. In an “is-a” type of inheritance, new classes build upon existing (base) classes as a starting point. The role of the base class is to house all of the common functionality for the classes that extend it. For example a Person class houses the shared details about a person. From a Person you extend the code to create a Student (is-a person), a Teacher (is-a person) and so on.

The class that serves as a basis for a new class is called a base class, super class or parent class. The class extending the base class is called the child or derived class. To define a new class that inherits some base class, in C#, you use the colon operator between the child and base class names, like so:

// a Student is-a Person
public class Student : Person
{
}

Multiple Inheritance

In C#, a given class can only have one direct base class. In other words, a class can only extend one class. It can, however, implement multiple interfaces. This allows for building more sophisticated interface hierarchies to model complex behaviors.

Next, we will see some sample code.

public class Person
{
    private string _firstName;
    private string _lastName;

    public string FirstName
    {
        get => _firstName;
        set
        {
            if (value.Length > 50)
            {
                throw  new ArgumentException("First Name is longer than then allowed 50 characters", 
                    nameof(FirstName));
            }

            _firstName = value;
        }
    }
    public string LastName
    {
        get => _lastName;
        set
        {
            if (value.Length > 50)
            {
                throw new ArgumentException("First Name is longer than then allowed 50 characters",
                    nameof(LastName));
            }

            _lastName = value;
        }
    }
    public DateTime DateOfBirth { get; set; }
    public string Gender { get; set; }
}

Notice that FirstName and LastName are enforcing encapsulation and business rules. A child class cannot access private members of a parent class. In other words, any class extending Person cannot access Person._firstName and Person._lastName directly.

An alternative to this is to change the Private members to Protected. Protected members are directly accessible to all descendants of a class. The downside of this is that it bypasses business rules and violates encapsulation. Next we add a Student class that extends Person:

// a Student is-a Person
public class Student : Person
{
    public int StudentNumber { get; set; }
    public String Type { get; set; }

}

By extending Person, the Student class now has the same public members as the Person class. This table shows the shared members:

Inheritance in C#
    static void Main(string[] args)
    {
        Student student = new Student();

        student.FirstName = "Frank";
        student.LastName = "Jones";
        student.DateOfBirth = new DateTime(1992, 12,1);
        student.StudentType = "Full Time";
    }

Constructors

Classes never inherit the constructors of a parent class. Constructors will only initialize the class in which they are defined. Although a child class can call a parent’s class constructor, it does not initialize the child class.

If we add a constructor to the Person class that initializes its properties, then we can call that constructor form the Student class, like so:

public class Person
{

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
    private string _firstName;
    private string _lastName;
    ...
}

The child class can call the parent’s class constructor and pass the appropriate parameters by using chaining. In the example below, the constructor for the Student class takes the parameters for first and last names. It chains the constructor of the base class by using : base(firstName, lastName) . This allows the child class to initialize the parent’s class properties via its own constructor.

public class Student : Person
{
    public int StudentNumber { get; set; }
    public string StudentType { get; set; }

    public Student(string firstName, string lastName, string studentType) 
        : base(firstName, lastName)
    {
        StudentType = studentType;
    }
}

Capping the Chain of Inheritance

Some classes should not be extended. When you create a class and decide that class should not be extended, you can decorate the class with the Sealed keyword. By marking a class Sealed, any attempt to extend that class will produce a compile time error.

Implicit Inheritance

In .Net, all types implicitly inherit from System.Object. All .Net types have the common functionality provided by System.Object. This provide some basic functionality for all types, such as Equals(Object), GetType() and ToString().

Leave a comment

Your email address will not be published. Required fields are marked *