O'Reilly logo

Programming C# 4.0 by Jesse Liberty, Matthew Adams, Ian Griffiths

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Inheritance and Polymorphism

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.

Warning

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.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required