Inheritance

Inheritance

The Crash Course

  • Inheritance is a way to reuse classes by expanding them into more specific types of classes. For instance, a Square class can be derived (or inherit from) a Polygon class, giving it all of the features that the Polygon class has.
  • Any class can be used as the base class.
  • In another class, to specify a base class, you use the colon followed by the class name. For example class Square : Polygon { /* … */ }.
  • The protected access modifier means that anything within the class can use it, as well as anything in any derived class.
  • You can prevent a class from being derived from by using the sealed keyword: sealed class Square { /* … */ } makes the Square class unable to be inherited from.

Introduction

Imagine that you are trying to keep track of geometric polygons, like triangles, rectangles, squares, pentagons, and so on. You can probably easily imagine creating a Polygon class that has, say, the number of sides the polygon has, and maybe an array of the positions of the vertices (corners) of the polygon.

Your Polygon class could be used to represent any of these polygons, but let's imagine that for a square, you want to be able to do some extra things with it. For instance, create one using only a size (since the width and height are the same length in a square), or possibly you want to be able to create a method that returns the area of the square.

One approach would be to create a Square class, which has everything the Polygon class has, plus the extra stuff you want. The problem with this is that now you have an entirely different class, and if you want to make a change in the way people work with polygons, then you'll also want to make a change in the way they work with squares. More than that, while you could create an array of polygons (Polygon[] polygons) you wouldn't be able to put squares in that list, even though they are polygons, and should be able to be treated as polygons.

After all, a square is a polygon.

And in fact, that is getting right to the heart of what we're going to discuss. A square is a polygon, but more specifically, it is a special type of polygon that has its own special things. But anything a polygon can do a square can do too, because a square is a polygon.

This is what programmers call an "is-a" relationship. It is so common that programming languages create constructs to facilitate this kind of thing.

In C#, the construct that lets us do this is called inheritance. Inheritance allows us to create a Polygon class, and a Square class that is based on the Polygon class, and reuse ("inherit") everything from the Polygon class that it is based on. This means any changes we make to the Polygon class will also be automatically changed in the Square class.

Base Classes

A base class (or superclass or parent class—all of these terms are all very commonly used, though we'll stick with "base class" in these tutorials) is any normal class (like any of the ones we've made before) that is used by another for inheritance. In our discussion, the Polygon class would be a base class.

So for instance, we could have a class that looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Inheritance
{
    class Polygon
    {
        public int NumberOfSides { get; set; }
 
        public Polygon()
        {
            NumberOfSides = 0;
        }
 
        public Polygon(int numberOfSides)
        {
            NumberOfSides = numberOfSides;
        }
    }
}

Derived Classes

A derived class (or subclass) is one that is based on another class. In our example, the Square class would be a derived class that comes from the Polygon class. We would say that the Square class is derived from the Polygon class.

You can create a derived class just like any other class with one small difference, which indicates what class is the base class.

While a "normal" class would be created like this:

class Square
{
    //...
}

To indicate that the Square class is derived from the Polygon class, we use the colon (':') and name the base class:

class Square : Polygon
{
    //...
}

Inside of the class, we simply indicate any new stuff that the Square class has that the Polygon class doesn't have. So our completed Square class might look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Inheritance
{
    class Square : Polygon
    {
        public float Size { get; set; }
 
        public Square(float size)
        {
            Size = size;
            NumberOfSides = 4;        
        }
    }
}

So now a square will have a Size property, but in addition, it inherits instance variables, properties, and methods that were in the Polygon class, so the Square class also has a NumberOfSides property that it inherits.

One other thing that would should mention here is that a class can inherit from a different class that inherits from yet another class. You can have as many layers as you want.

Using Derived Classes

There's one very important thing that we need to talk about when it comes to using derived classes.

As you've probably guessed, you can create a Polygon object in your program, and you can also create a Square object in your program, exactly like we've seen before:

Polygon polygon = new Polygon(3); // a triangle
Square square = new Square(4.5f); // a square, which is a polygon with 4 sides of length 4.5.

But since a Square is a Polygon, you can also have a variable that has Polygon as its type, but is actually storing a Square. That's fine, because a Square is a polygon!

Polygon polygon = new Square(4.5f);

As the program is running, though, when we're using the polygon variable, we only have access to the stuff that a Polygon has. So for example, we can check to see the NumberOfSides it has, but as the program is running, we can't know if a Polygon typed variable is keeping track of a Polygon or a Square or perhaps yet another type, like a Parallelogram or something. We just know that it is a Polygon, and so we can only work with the things a Polygon has.

Checking an Object's Type and Casting

As I just described, it is possible to be working with a derived type, like the Square, but only know that it is a Polygon. Sometimes, we need to be able to figure out what type an object is specifically, and work with it differently.

It is possible to check to see if an object is a specific type, with the is keyword and casting:

Polygon polygon = new Square(4.5f);
 
if (polygon is Square)
{
    Square square = (Square)polygon;
    // We can now do what we want with the square.
}

Using Inheritance in Arrays.

In light of what we've just been discussing, it is worth mentioning that you can create an array of the base class (for instance, Polygon[] lotsOfPolygons = new Polygon[5];) and put any derived class inside of it (lotsOfPolygons[2] = new Square(2.1f);).

For all intents and purposes, a derived class like Square can always function in the same capacity as its base class (Polygon).

Constructors and Inheritance

I mentioned earlier that in a derived class, all properties, instance variables, and methods are inherited. Constructors, on the other hand, function a little bit differently.

You can't use the constructors in the base class to create a derived class. For instance, the Polygon class has a constructor that takes an int for the number of sides for the polygon. You can't create a Square using that constructor. And for good reason. It just doesn't make sense. If you could create a new Square with something like Square square = new Polygon(6);, setting the number of sides to 6, everything would be all messed up.

You can only create an instance of an object from the constructors the derived class defines.

There's another part to the constructor stuff as well. Since a derived class is based on another class, as we're creating a derived class, we will also need to choose a constructor to use from the base class. By default, the parameterless constructor is chosen. (For example, Polygon().

(Even if you don't have any declared constructors, remember that the C# compiler will automatically create a default, parameterless constructor for you.)

However, you don't need to stick with the default constructor, or perhaps your base class doesn't have a parameterless constructor.

In either of these two cases, you can choose any of the base class's constructors in the derived class's constructors, using the base keyword. The constructor we originally made in the Square class would be much better defined using this technique, shown below:

public Square(float size)
        : base(4)
{
    Size = size;
}

Here, we put a colon (':'), followed by the base keyword, along with the parameters for the constructor from the base class that we want to use.

Doing this, we can eliminate the parameterless constructor from the Polygon class, which (unfortunately) has been allowing people to make Polygons without actually choosing a number of sides.

The protected Access Modifier

In the past, we discussed the private and public access modifiers. To review, remember that public meant anyone could get access to the method or variable, while private means that you only have access to it from inside of the class that it belongs to.

With inheritance we have another option: protected. If a variable, property, or method is marked with protected instead of private or public, then you'll only have access to it inside of the class or any derived class.

So, for instance, if the Polygon class made the NumberOfSides property protected instead of public, then you could access it from anywhere in the Polygon class, as well as the Square class, and any other derived class, but not from anything outside of those.

The Base Class of Everything: object

Without specifying any class to inherit from, any class you make still is derived from a special base class that is sort of the base class of everything. This is the object class.

Also, there's an object keyword that works a lot like the int or float types that we've seen before. Only, the object keyword will always represent an object. (Like any class, it is a reference type, rather than a value type.) But because of the properties of inheritance, it can represent any object.

So remember what I said earlier about how you can store a derived class in a base type like the code below?

Polygon polygon = new Square(4.5f);

You can also put it in the object type:

object anyOldObject = new Square(4.5f);

Since object is the base class of all classes, any type of object can be stored in it.

Additionally, the object class defines a few methods for you, that every single object that you ever create will have. There are about four total, but one, in particular, that is worth looking at is the ToString() method. All objects have a ToString() method, which means you can call it any time you're working with an object.

Preventing a Class from Being Inherited

There may come a time where you want to prevent a class from being inherited. For instance, you want to ensure that no one derives anything from a specific class.

You can use the sealed keyword to make it so nothing can inherit from a specific class by adding it to your class definition:

sealed class Square : Polygon
{
    // ...
}

Sealing a class can result in a performance boost.

Structs are sealed by default, and cannot be unsealed, so inheritance with structs is impossible.

What's Next?

Inheritance provides a very powerful mechanism for organizing code, and making classes more reusable. But it can also be a complicated issue, and in fact, we're more or less going to continue the inheritance discussion in the next tutorial, as we look at it from a different point of view, and discuss abstract classes and polymorphism.