10 min read

Inheritance

Crash Course

Introduction

Sometimes, one class of objects has everything another class has, and then some–where all things of type X are also of type Y.

For example, in an RPG game, you might include an inventory system. All inventory items have a weight (and the character can only carry so much weight). The game also has weapons. All weapons are inventory items, and will have a weight. Weapons may have other abilities and data, but at a minimum we know they’ll have weight, because they are inventory items.

Inheritance is a tool that lets us define this “specialization” relationship between two types–where we can indicate that one class we’re defining is a special kind of some other class, and includes everything the other type has (plus more).

It is a powerful tool that allows us to eliminate duplicate code, and work with objects that have different, but overlapping types.

Defining an Inheritance Relationship

To establish an inheritance relationship between two types, we start by defining the base class–the more fundamental or basic class. There is nothing special about these classes–they’re just a normal class:

class InventoryItem
{
    public float Weight { get; }

    public InventoryItem(float weight)
    {
        Weight = weight;
    }
}

Things get interesting when we make another type extend or build upon this base class. The following Sword class is considered the derived class :

class Sword : InventoryItem
{
    public float Length { get; }
    public int Damage { get; }

    public Sword(float length, int damage, float weight) : base(weight)
    {
        Length = length;
        Damage = damage;
    }
}

That little code snippet covered a whole lot of ground, so let’s analyze it.

The main feature is the part at the end of the first line: : InventoryItem. This part of the code is what says, “All swords are also inventory items, so include everything that is a part of inventory items in general.”

The Sword class has two of its own properties (both read-only after construction, because they don’t have a setter): Length and Damage. These two properties are also initialized in the Sword constructor. Otherwise, though, these properties are not doing anything we haven’t seen before.

Which leaves us with only one remaining thing to cover: the : base(weight) that comes after the Sword constructor is defined. The InventoryItem class’s constructors are responsible for getting an inventory item into a good starting state. That means that the derived class–Sword in this case–does not know, on its own, exactly how to get those parts of the sword into a good starting state. Instead, it must specifically call one of the constructors defined in the base class. That is what the base(weight) does. This indicates that as the Sword constructor begins running, call the constructor in the base class that has a single float parameter (which happens to also be its only constructor). Once that constructor finishes, then the Sword constructor can run, putting its own properties into a good starting state.

note

It is relatively common to have a derived class’s constructor have parameters that are simply passed along to the base class, as we see here with the weight property. But that isn’t the only approach. For example, if all swords had the same weight, we could have left off the Sword class constructor’s weight parameter and passed in a specific weight to the base class:

public Sword(float length, int damage) : base(3)
{
    /* ... */
}

Or maybe the weight of the sword depends on its length:

public Sword(float length, int damage) : base(length * 1.25f)
{
    /* ... */
}

There is one situation where you don’t need to pick a base class constructor: when the one you want to use is the parameterless constructor. If the base class has a constructor with no parameters (for example, public InventoryItem() { ... }) and if this is the constructor you want to call from the derived class, you don’t need to call it out specifically. You can leave off the base().

One important note about constructors: if you don’t define any constructors for a class, it will automatically make a parameterless one for you that does nothing. Basically, it automatically generates this:

public InventoryItem()
{
}

Once you put your own constructor in–either one without parameters or one with many parameters–then that default one will not be created automatically.

All of this means that the following works fine, even though there are no constructors defined:

class BaseClass
{
    public int Number { get; set; }
    public void DoSomething() { /* ... */ }
}

class DerivedClass : BaseClass
{
    public int AnotherNumber { get; set; }
}

BaseClass will end up with a parameterless constructor, and you can call it with new BaseClass(), even though you don’t actually see it written out within the BaseClass definition. The same thing happens for DerivedClass, and you’ll be able to call that with new DerivedClass(). But that constructor will automatically call the parameterless constructor in BaseClass for you.

Using Classes that Involve Inheritance

Where things get especially interesting is in how we can use instances of classes that use inheritance.

This probably won’t surprise you:

InventoryItem item = new InventoryItem(20);

This shouldn’t either:

Sword sword = new Sword(1.5f, 10);

But this might:

InventoryItem item = new Sword(1.5f, 10);
Console.WriteLine(item.Weight);

The variable item has a type of InventoryItem, but actually contains a reference to a Sword!

But that’s okay, because swords can do everything inventory items can do, and more. Of course, since the type of the variable item is InventoryItem, we will only be able to access the methods and properties defined in the InventoryItem class. We wouldn’t be able to do item.Damage, because it can only guarantee that item holds a reference to an inventory item, and not a sword, specifically.

The key point is substitutability. Any time the base class type is expected, you can put in an instance of any derived class.

Inheritance Hierarchies

Every class you make has the option of picking another class as its base class. That means you could have one base class with 6 or 600 derived classes. (It is easy to imagine coming up with other things besides Sword that might derive from InventoryItem, for example.)

You can also have multiple layers in your hierarchy. Our Sword class, above, included a Damage property. If we had many weapons that all needed to represent damage, you could imagine making a Weapon class that derives from InventoryItem. So Sword could derive from Weapon, which itself derives from InventoryItem.

One important thing to note is that classes can only have a single base class.

The object Class

If your class doesn’t specifically name any base class, then it will implicitly derive from a special class called object. object is essentially the base class of every object you could ever create.

Which means you could do something like this:

object anything = new Sword(1.5f, 10);

And you could do this:

object anything = "Hello World!";

And you can even do this:

object anything = new Sword(1.5f, 10);
Console.WriteLine(anything);

anything = "Hello World!";
Console.WriteLine(anything);

See that? Because inheritance allows you to use one of the derived types whenever the base type is expected, we can store a reference to a Sword in anything at one point, and then later store a reference to a string in it.

The catch is that, because the variable’s type is object, there’s very little you can do with it. A string has a Length property, which can tell you how many characters is in it. You won’t have access to that. A Sword has that Damage property, which tells you how much damage it delivers when used. You won’t have access to that.

The object class has very little. (Though it does have a ToString method, that returns a string representation of the object.)

In short, when you use a base class for a variable or parameter, etc., it will be flexible enough to be used for any derived types. In exchange, you can only use it for things that are known at the base class level. You won’t be able to use anything that any of the individual derived classes has. So you must find the balance. (Which isn’t always easy.)

Moving Up and Down the Inheritance Hierarchy

From a more specialized derived class, your program will always be able to automatically convert to the base class, as we have seen:

InventoryItem item = new Sword(1.5f, 10);

If you want to go the other way, you have a few choices.

If, through some other logic or reasoning, you know something is the more specialized type, you can use a cast to convert to the derived class:

InventoryItem item = GetASword();
Sword sword = (Sword)item;

This will cause your program to crash if you’re wrong, so only do this if you’re sure it is true.

You can check if an object is some other type with the is keyword:

InventoryItem item = GetAnItem();

if (item is Sword) Console.WriteLine("The item was a sword.");

The is keyword also includes an option to automatically place the value into a new variable if the type is right:

if (item is Sword s)
    Console.WriteLine("This is a good sword. It is " + s.Length + " units long and deals " + s.Damage + " damage when used.");

The protected Accessibility Modifier

While we saw the public and private accessibility modifiers a few tutorials ago, there’s another one that is useful in inheritance situations: protected.

If something is marked protected, then it can be used within the class, and also within any derived classes, but not outside of the inheritance hierarchy.

Here’s an illustration, which makes a protected constructor, which can be called only by derived classes:

class InventoryItem
{
    public float Weight { get; protected set; }

    protected InventoryItem(float weight)
    {
        Weight = weight;
    }
}

class Sword : InventoryItem
{
    public float Length { get; }
    public int Damage { get; }

    public Sword(float length, int damage) : base(1.5)
    {
        Length = length;
        Damage = damage;
    }
}

Because InventoryItem’s constructor is protected, the outside world won’t even be able to make new instances of InventoryItem directly! It isn’t accessible! Instead, they’ll have to make instances of a derived type, like Sword, instead, which does have the ability to call that protected constructor.

note

The protected modifier can go on any member in a class, the same as a field, property, or method.

Sealed Classes

If you want to prevent other classes from inheriting from a class you have made, you can place the sealed keyword on the class itself:

sealed class Sword
{
    // ...
}

There are some slight performance gains that can be made with a sealed class, but this is more about saying, “It isn’t good to derive from this class, so I’m going to prevent it.” You probably won’t do that very often, but it is there if you need it.

caution

Any discussion of inheritance deserves a cautionary note. Inheritance is an extremely powerful tool, and you should put it to good use when it makes sense. But inheritance is a very strong relationship between two types.

Inheritance should never be applied just for the purpose of code reuse or removing duplicate code. You only want to apply inheritance when what you’re building is truly a specialization of something else.

You should also consider what is called a has-a relationship, where, say, object A has an instance of type B. A has-a relationship is done by making the thing have a field (or property) of that other type.

There are other ways for objects of one type to utilize objects of another type, including passing one in as a parameter to a method.

Furthermore, very deep and expansive inheritance hierarchies can get unwieldy to manage. Suppose you have a class called A as a base class, with B and C deriving from it. D and E derive from C, and F derives from E. (So ultimately, F derives from E, which derives from C, which derives from A.) Now imagine that you’re making another thing, G, that is a specialization of B, but it needs some features that E has. You can sometimes juggle the whole type hierarchy around to make it work, but it is often not obvious how to do this.

Inheritance is powerful–especially when combined with some of the more advanced features that we’ll see in the next tutorial–but keep an eye on if it is serving your purposes well, or just making things difficult.