We’ll get into the nuances of the question in the preceding paragraph in a minute, but let’s assume for the time being that our answer to the question is yes (which, on face value, seems reasonable). Example 4-3 shows how we use inheritance in C#.
Example 4-3. Inheritance in C#
class FireChief : Firefighter { public void TellFirefighterToExtinguishFire (Firefighter colleague) { colleague.ExtinguishFire(); } }
Notice that we use the colon in the class declaration to indicate
that FireChief
is a Firefighter
. We then say that Firefighter
is a base class of FireChief
. Looking at the relationship from the
other direction, we can also say that FireChief
is a derived class of Firefighter
.
We’ve added the extra function that allows the chief to tell a firefighter to extinguish a fire—which encapsulates that extra responsibility. What we haven’t had to do is to duplicate all the functionality of the firefighter; that comes along anyway.
We can now use the fire chief just as we would a firefighter, as shown in Example 4-4.
Example 4-4. Using base class functionality inherited by a derived class
Firetruck truckOne = new Firetruck(); FireChief bigChiefHarry = new FireChief { Name = "Harry" }; truckOne.Driver = bigChiefHarry; bigChiefHarry.Drive(truckOne, new Point(100,300)); Firefighter joe = new Firefighter { Name = "Joe" }; bigChiefHarry.TellFirefighterToExtinguishFire(joe);
Because bigChiefHarry
is an
object of type FireChief
, and a
FireChief
is a Firefighter
, we can assign him to be the driver
of a truck and tell him to drive it somewhere. But because he is a
FireChief
, we can also ask him to tell
Joe to put out the fire when he gets there.
Wherever we talk about a FireChief
, we can treat the object as a Firefighter
. This use of one type as though it
were one of its bases is an example of polymorphism.
Equally, we could phrase that the other way around: we can successfully substitute an instance of a more-derived class where we expect a base class. This is known as the Liskov Substitution Principle (LSP) after computer scientist Barbara Liskov, who articulated the idea in a paper she delivered in 1987.
It is quite possible to derive one class from another in a way that means we can’t treat the derived class as its base type. The derived class could change the meaning or behavior of a function with the same signature as its base, or throw errors in situations where the base promised that everything would be fine—say, the base accepted parameters in the range 1–10, where the derived class accepts parameters in the range 2–5.
This violates the LSP, which is a very poor design practice, but it is very easy to slip into, especially if the classes evolve independently over time.
What happens if our client doesn’t know that Harry is a fire chief,
though? What if we refer to the object via a reference typed to Firefighter
instead?
FireChief bigChiefHarry = new FireChief { Name = "Harry" }; // Another reference to Harry, but as a firefighter Firefighter stillHarry = bigChiefHarry; Firefighter joe = new Firefighter { Name = "Joe" }; stillHarry.TellFirefighterToExtinguishFire(joe);
You know that stillHarry
is
referencing an object that is a FireChief
, with that extra method on it. But the
compiler produces a long, semicomprehensible error full of useful
suggestions if you try to compile and execute this code:
'Firefighter' does not contain a definition for 'TellFirefighterToExtinguishFire' and no extension method 'TellFirefighterToExtinguishFire' accepting a first argument of type 'Firefighter' could be found (are you missing a using directive or an assembly reference?)
The compiler is being rather tactful. It is assuming that you must’ve forgotten to include some external reference that’s got a suitable extension method definition to fix your problem. (We’ll be looking at that technique in a later chapter, by the way.)
Unfortunately, the real reason for our bug is hidden in the error’s
opening salvo: we’re trying to talk to a FireChief
method through a
variable that is strongly typed to be a Firefighter
, and you can’t call on any members
of the derived class through a reference typed to a base.
So, if we can’t use a derived member from a reference to a base
type, is there any way we can refine these classes so that Harry never
puts out a fire, but always passes responsibility to his Number One when
he’s asked to do so, regardless of whether we happen to know that he’s a
FireChief
? After all, he
knows that he’s the boss!
To get started, we’ll have to make a few changes to the model to
accommodate this idea of the chief’s Number One. In other words, we need
to create an association between the FireChief
and his NumberOne
. Remember that we typically implement
this as a read/write property, which we can add to the FireChief
:
public Firefighter NumberOne { get; set; }
And let’s change the main function so that it does what we want (see Example 4-5).
Example 4-5. Using base class methods to keep the compiler happy
// A reference to Joe, Harry's number one Firefighter joe = new Firefighter { Name = "Joe" }; // Firefighter harry is really a firechief, with joe as his NumberOne Firefighter harry = new FireChief { Name = "Harry", NumberOne = joe }; // Harry is just a firefighter, so he can extinguish fires // but we want him to get joe to do the work harry.ExtinguishFire();
But if we compile that, here’s the output we get:
Harry is putting out the fire!
That’s not what we want at all. What we want is a different
implementation for that ExtinguishFire
method if we’re actually a FireChief
, rather than an ordinary Firefighter
.