Thus Far and No Farther: sealed

Having got through all of that, you’re probably rightly concerned that, simple though it is in theory, the practical implications of inheritance are actually rather complex and require a lot of documentation, testing, and imagining how people might use and abuse your virtual methods. And we have to do that for every class down the hierarchy.

When we designed our FireChief, we happily provided an override for the ExtinguishFire method, without giving a thought for the fact that someone else might override that method in his own derived class. In fact, we didn’t even consider the possibility that anyone might derive from FireChief at all. No documentation, nothing.

Now there are several members on our own base class that could be overridden by a class that derives from FireChief. Does that have any implications for our own documentation or testing? Can we even tell? And how could we have guessed that was going to happen when we built our FireChief class, since there was only one virtual member on the base at that time? This looks like it has the potential to become a rich future source of bugs and security holes.

Fortunately, we can eliminate this problem at a stroke by saying that we didn’t design our FireChief to be derived from, and stopping anyone from doing so. We do that by marking the FireChief class sealed. Let’s see how that looks:

sealed class FireChief : Firefighter
{
    // ...
}

We apply the sealed modifier before the class keyword and after any accessibility modifiers if they are present.

So, what happens if we try to derive a new class from FireChief now?

class MasterChief : FireChief
{
}

Compile it, and you’ll see the following error:

'MasterChief': cannot derive from sealed type 'FireChief'

That’s put a stop to that. Let’s delete our MasterChief so that everything builds again.

Warning

Not only can sealing classes be very useful (and defensive), but if you decide later that you want to unseal the class and allow people to derive their own types, it doesn’t (normally) break binary compatibility for the type. Sealing a previously unsealed class, however, does break compatibility.

We now have three different types of firefighter. Let’s remind ourselves how they are related (see Figure 4-1).

Those three types of firefighter basically differ in the strategy that they use for putting out fires. There’s a base class that provides a default implementation, and a couple of classes that override the virtual methods to do things differently.

Let’s say we wanted to support lots of different types of firefighter, all of whom were expected to have a different approach to fighting fire, from the trainee, to the chief, to Gulliver (who has his own idiosyncratic way of putting out a fire in Lilliput).

We still want the handy Name property and the Drive method, and we still want anyone to be able to call an ExtinguishFire method.

Noticing that our FireChief, for example, doesn’t make use of the base implementation at all; we don’t want to provide a standard for that method. We’ll just let all implementers decide for themselves how it is going to work.

We’re shooting for something that looks like Figure 4-2.