In the previous chapter, we saw how to define various types of classes and specify their members—fields, properties, and functions.
In this chapter, we’re going to start by looking at this again in more detail, and try to understand what underlying concepts we’re implementing when we use these different coding patterns. We’ll then introduce a couple of new concepts—inheritance and polymorphism—and the language features that help us implement them.
We’ve finished our ATC application, by the way. Having gotten a reputation for building robust mission-critical software on time and to spec, we’ve now been retained by the fire department to produce a training and simulation system for them. Example 4-1 shows what we have so far.
Example 4-1. Classes representing firefighters and fire trucks
class Firefighter { public string Name { get; set; } public void ExtinguishFire() { Console.WriteLine("{0} is putting out the fire!", Name); } public void Drive(Firetruck truckToDrive, Point coordinates) { if (truckToDrive.Driver != this) { // We can't drive the truck if we're not the driver // But just silently failing is BADBAD // What we need is some kind of structured means // of telling the client about the failure // We'll get to that in Chapter 6 return; } truckToDrive.Drive(coordinates); } } class Firetruck { public Firefighter Driver { get; set; } public void Drive(Point coordinates) { if (Driver == null) { // We can't drive if there's no driver return; } Console.WriteLine("Driving to {0}", coordinates); } }
We have a model of the Firetruck
,
which uses a Firefighter
as its Driver
. The truck can be instructed to drive
somewhere (if it has a driver), and you can tell a Firefighter
to drive the truck somewhere (if
he is the designated driver).
You can think of this as modeling a relationship between a Firetruck
and its Driver
. That driver has to be a Firefighter
. In object-oriented design, we call
this relationship between classes an
association.
An association is a kind of flexible, “arms length” relationship between two entities in the system. There are no particular constraints about the direction of the relationship: the firefighter can be associated with the truck, or the truck with the firefighter. Or both.
Any particular firefighter may have associations with other types, and we can always assign another driver to the fire truck; there’s no exclusivity. For instance, we can do something like this:
Firetruck truckOne = new Firetruck(); Firefighter joe = new Firefighter { Name = "Joe" }; Firefighter frank = new Firefighter { Name = "Frank" }; truckOne.Driver = joe; // Later... truckOne.Driver = frank;
But what about the 30 foot retractable ladder that we happen to have on the fire truck; what kind of relationship exists between the ladder and the fire truck?
Here’s our ladder class:
class Ladder { public double Length { get; set; } }
This particular ladder is one of those powered, extensible rotating things that are built right into the truck. So let’s add a property to represent that (see Example 4-2).
Example 4-2. Fire truck with integral ladder
class Firetruck { public Firefighter Driver { get; set; } readonly Ladder ladder = new Ladder { Length = 30.0 }; public Ladder Ladder { get { return ladder; } } // ... }
When we construct the Truck
, it
creates a 30-foot ladder for itself, with a read-only property to retrieve
it.
We call this “made of” association between classes composition. The ladder is a built-in part of the fire truck, but the fire truck can never be a part of the ladder, and the truck itself is responsible for the life cycle of its own ladder.
What if we need to manage other equipment on the truck—a detachable coil of hose, for example:
class Hose { }
We could add a property to the Truck
to get and set that (modeling a particular
coil of hose being connected to the hose system on the truck):
public Hose Hose { get; set; }
This is another kind of composition relationship—one component of
the Truck
is a hose, and the truck
certainly can’t be a part of the hose; but the containing object (the
truck) no longer controls the creation and lifetime of its own piece of
apparatus. Instead, we say that it aggregates the hose.
Of course, there are no hard-and-fast rules about these terms and the code you write; they are just concepts which we use when we are designing systems. The definitions we’ve used come from the Unified Modeling Language (UML) 2.0, and we’re just mapping them to C# language features.
Nonetheless, it is useful to have a common conceptual language for describing our systems and the common characteristics of the code we use to implement them. Equally, when you are looking at someone else’s code (remembering that “someone else” includes “past you”) it is helpful to be able to translate what was written into these standard modeling concepts.
So we have a software model of the Firetruck
, which has a Ladder
and a Hose
and uses a Firefighter
as its Driver
. What about the fire chief?
The fire chief is just another firefighter. He can drive a truck. He can put out fires. But he can do other stuff too. For instance, he can delegate responsibility for putting out a fire to another firefighter.
The question we ask ourselves is this: is the FireChief
a Firefighter
with extra responsibilities? If the answer is yes, we
are describing an is-a association (the FireChief
is
a Firefighter
) which we can
represent by an inheritance relationship.
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
.
So the implementation for the ExtinguishFire
method that we want on the
FireChief
looks like this:
public void ExtinguishFire() { // Get our number one to put out the fire instead TellFirefighterToExtinguishFire(NumberOne); }
What happens if we just add that function to our FireChief
and compile and run?
Well, it compiles, but when we run it, it still says:
Harry is putting out the fire!
It seems to have completely ignored our new function!
Let’s go back and have a look at that compiler output again. You’ll see that although it built and ran, there’s a warning (you may have to rebuild to get it to appear again; Choose Rebuild Solution from the Build menu):
'FireChief.ExtinguishFire()' hides inherited member 'Firefighter.ExtinguishFire()'. Use the new keyword if hiding was intended.
Note
It is a good idea to leave all your compiler warnings on and work until you are both error and warning free. That way, when something crops up unexpectedly like this, you can spot it easily, rather than burying it in a pile of stuff you’re habitually ignoring.
It is telling us that, rather than replacing
the implementation on the base class, our method (with matching signature)
is hiding it; and that if this is what we really
meant to do, we should add the keyword new
to the
method.
public new
void ExtinguishFire()
{
// Get our number one to put out the fire instead
TellFirefighterToExtinguishFire(NumberOne);
}
We typically add the new
modifier between the accessibility modifier and the return value.
Compile and run again. You’ll notice that we’ve gotten rid of the warning, but the output hasn’t changed:
Harry is putting out the fire!
What’s going on?
This method-hiding approach is actually letting a single object
provide different implementations for the ExtinguishFire
method. The implementation we
get is based on the type of the variable we use, rather than the type of
object to which the variable refers. You can see that happening if we
use the code in Example 4-6 in our
client.
Example 4-6. Different reference type, different method
// 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 FireChief harry = new FireChief { Name = "Harry", NumberOne = joe }; Firefighter harryAsAFirefighter = harry; // Harry is just a firefighter, so he can extinguish fires // but as a firechief he gets joe to do the work harry.ExtinguishFire(); // While as a firefighter he does it himself harryAsAFirefighter.ExtinguishFire();
The output we get now looks like this:
Joe is putting out the fire! Harry is putting out the fire!
When we talk to our Harry
object through a FireChief
reference,
he gets Joe
to put out the fire. If
we talk to the object through a Firefighter
reference, he does it himself.
Same object, but two completely different implementations.
Why might we want to do that?
Let’s say we had multiple fire chiefs on a job, but it is our policy that a chief acting as another chief’s Number One is not allowed to delegate the job again. Our code models exactly this behavior, as shown in Example 4-7.
Note
Of course, whether that’s desirable behavior is another matter entirely—we’ve ended up with such radically different approaches to putting out a fire that it might be better to separate them back out into functions with different names.
When you go through a refactoring process such as this, it is a good idea to check that you’re still happy with the semantic implications of your code. Ideally, you want to end up with a neat design, but a superficially neat design that makes no sense is not helpful.
Example 4-7. Making twisted use of method hiding
// A reference to Joe, Harry's number one Firefighter joe = new Firefighter { Name = "Joe" }; // FireChief harry has joe as his NumberOne FireChief harry = new FireChief { Name = "Harry", NumberOne = joe }; FireChief tom = new FireChief { Name = "Tom", NumberOne = harry }; // Harry is just a firefighter, so he can extinguish fires // but as a firechief he gets joe to do the work harry.ExtinguishFire(); // But if Tom is asked to extinguish a fire, he asks Harry to do it // Our policy dictates that Harry has to do it himself, not delegate to // Joe this time. tom.ExtinguishFire();
Harry delegates to Joe when he is asked to do it himself, because
we are calling through a reference to a FireChief
.
Tom is also a FireChief
, and we
are calling through a reference to him as a FireChief
, so he delegates to Harry; but when
Harry is asked to do it in his role as a Firefighter
(remember, the NumberOne
property is a reference to a
Firefighter
), he does it himself,
because we are now calling the method through that reference typed to
Firefighter
.
So our output looks like this:
Joe is putting out the fire! Harry is putting out the fire!
That’s all very well, but we don’t actually want that restriction—the fire chief should be allowed to pass the work off to his subordinate as often as he likes, regardless of who he asked to do it.
Warning
There’s one big caveat regarding everything we’ve just shown about method hiding: I can’t think of the last time I used this feature in a real application, but I see the warning from time to time and it usually alerts me to a mistake in my code.
We’ve wanted to illustrate how method hiding works, but we
discourage you from using it. The main reason to avoid method hiding
with new
is that it tends to
surprise your clients, and that, as we’ve established, is not a good
thing. (Would you really expect behavior to change because the type of
the variable, not the underlying object, changes?)
While method hiding is absolutely necessary for some corner cases, we usually treat this warning as an error, and think very carefully about what we’re doing if it comes up. 9 times out of 10, we’ve got an inadvertent clash of names.
What we actually want to do is to change the
implementation based on the type of the object itself, not the variable
we’re using to get at it. To do that we need to replace or
override the default implementation in our base
class with the one in our derived class. A quick glance at the C# spec
shows us that there is a keyword to let us do just that: override
.
Let’s switch to the override
modifier on the FireChief
implementation of the ExtinguishFire()
method:
public override void ExtinguishFire() { // Get our number one to put out the fire instead TellFirefighterToExtinguishFire(NumberOne); }
Notice that we removed the new
modifier and replaced it with override
instead. But if you compile, you’ll
see that we’re not quite done (i.e., we get a compiler error):
'FireChief.ExtinguishFire()': cannot override inherited member 'Firefighter.ExtinguishFire()' because it is not marked virtual, abstract, or override
We’re not allowed to override the method with our own
implementation because our base class has to say we’re allowed
to do so. Fortunately, we wrote the base class, so we can do
that (as the compiler error suggests) by marking the method in the base
with the virtual
modifier:
class Firefighter
{
public virtual
void ExtinguishFire()
{
Console.WriteLine("{0} is putting out the fire!", Name);
}
// ...
}
Why do we have this base-classes-opt-in system? Why is everything not virtual by default (like, say, Java)? Arguments continue on this very issue, but the designers of C# chose to go with the nonvirtual-by-default option. There are a couple of reasons for this: one has to do with implicit contracts, and another is related to versioning.
Note
There is also (potentially) a small performance overhead for virtual function dispatch, but this is negligible in most real-world scenarios. As always, test before optimizing for this!
We already saw how our public API is effectively a contract with our clients. With virtual functions, though, we are defining not only a contract for the caller, as usual, but also a contract for anyone who might choose to override that method. That requires more documentation, and a greater degree of control over how you implement the method.
Note
By declaring a method as virtual, the base class gives derived classes permission to replace whole pieces of its own innards. That’s a very powerful but very dangerous technique, rather like organ transplant surgery on an animal you’ve never seen before. Even a trained surgeon might balk at replacing the kidneys of a dromedary armed with nothing more than developer-quality documentation about the process.
For example, some method in your base class calls its MethodA
, then its MethodB
, to do some work. You then (perhaps
unknowingly) rely on that ordering when you provide overrides for
MethodA
and MethodB
. If a future version of the base class
changes that ordering, you will break.
Let’s go back to our example to look at that in more detail, because it is really important.
First, let’s change the implementation of Firefighter.ExtinguishFire
so that it makes
use of a couple of helper methods: TurnOnHose
and TrainHoseOnFire
(see Example 4-8).
Example 4-8. Virtual methods and method ordering
class Firefighter
{
// This calls TrainHoseOnFire and TurnOnHose as part of the
// process for extinguishing the fire
public virtual void ExtinguishFire()
{
Console.WriteLine("{0} is putting out the fire!", Name);
TrainHoseOnFire();
TurnOnHose();
}
private void TurnOnHose()
{
Console.WriteLine("The fire is going out.");
}
private void TrainHoseOnFire()
{
Console.WriteLine("Training the hose on the fire.");
}
// ...
}
Let’s also simplify our Main
function so that we can see what is going on, as shown in Example 4-9.
Example 4-9. Calling a virtual method
static void Main(string[] args) { // A reference to Joe, Harry's number one Firefighter joe = new Firefighter { Name = "Joe" }; joe.ExtinguishFire(); Console.ReadKey(); }
If we compile and run, we’ll see the following output:
Joe is putting out the fire! Training the hose on the fire. The fire is going out.
All is well so far, but what happens if we add a trainee
firefighter into the mix? The trainee is extremely fastidious and
follows his instructor’s guidelines to the letter. We’re going to make a
class for him and override the TurnOnHose
and TrainHoseOnFire
methods so that the work is
done in the trainee’s own particular idiom.
Hang on a moment, though! Our helper methods are private
members. We can’t get at them, except
from other members of our Firefighter
class itself.
Before we can do anything, we need to make them accessible to derived classes.
In the preceding chapter, we mentioned that there were two
additional accessibility modifiers that we would deal with later:
protected
and protected internal
. Well,
this is where they come into their own. They make members accessible to
derived classes.
If you want a member to be available either to
derived classes or to other classes in your own
assembly, you mark that member protected
internal
. It will be visible to other classes in the library, or
to clients that derive classes from your base, but inaccessible to other
clients who just reference your assembly.
If, on the other hand, you want your class to make certain methods
available only to derived classes, you just mark those methods protected
. In terms of code out there in the
wild, this is the most common usage, but it is not necessarily the best
one!
Note
Both protected internal
and internal
are much underused
access modifiers. They are a very convenient way of hiding away library
implementation details from your consumers, and reducing the amount of
documentation and surface-area testing you need.
I suspect that they are unpopular (as with most “hidden by
default” or “secure by default” schemes) because they can sometimes get
in your way. There are a fair number of implementation details of
classes in the .NET Framework that are internal
(or private
) that people would very much like to
access, for example.
A common reason for taking something useful and applying the
internal
modifier is that it was not
possible to fully document (or understand the full implications of) the
“hook” this would provide into the framework. And rather than open up
potential security or reliability problems, they are marked internal
until a later date: perhaps much,
much later, tending toward never. Although there is an intention to
revisit these things, real-world pressures mean that they often remain
unchanged.
This is another example of the “lock down by default” strategy which helps improve software quality.
That doesn’t make it any less irritating when you can’t get at the inner workings, though!
So we’ll mark those methods in the base class virtual
and protected
, as shown in Example 4-10.
Example 4-10. Opening methods up to derived classes
protected virtual void TurnOnHose()
{
Console.WriteLine("The fire is going out.");
}
protected virtual void TrainHoseOnFire()
{
Console.WriteLine("Training the hose on the fire.");
}
We can now create our TraineeFirefighter
class (see Example 4-11).
Example 4-11. Overriding the newly accessible methods
class TraineeFirefighter : Firefighter { private bool hoseTrainedOnFire; protected override void TurnOnHose() { if (hoseTrainedOnFire) { Console.WriteLine("The fire is going out.");
} else { Console.WriteLine("There's water going everywhere!");
} } protected override void TrainHoseOnFire() { hoseTrainedOnFire = true; Console.WriteLine("Training the hose on the fire.");
} }
As you can see, the trainee is derived from Firefighter
. We added an extra Boolean field to
keep track of whether the trainee has actually trained the hose on the
fire, and then provided our own implementations of TrainHoseOnFire
and TurnOnHose
that make use of that extra field.
This is intended to model the detailed but slightly peculiar and
occasionally erratic way in which the trainee follows the instructions for
these operations in his copy of How to Be a
Firefighter, rather than allowing common sense to
prevail.
We also need a quick update to our main function to use our trainee. Let’s add the following code at the end:
// A reference to Bill, the trainee Firefighterbill =
new TraineeFirefighter{ Name =
"Bill"};
bill.ExtinguishFire();
If we compile and run, we see the following output:
Joe is putting out the fire! Training the hose on the fire. The fire is going out. Bill is putting out the fire! Training the hose on the fire. The fire is going out.
Well done, Bill; all that training came in handy, exactly as we’d expect.
Although it works, you’ll notice that we’ve duplicated some code
from our base class into our derived class—the bit that actually does the
work in each of those methods. It would be better if we could just call on
our base class implementation to do the job for us. As you’d expect, C#
has this in hand, with the base
keyword.
If we ever want to call on the implementation of a member in
our base class (bypassing any of our own overrides), we can do so through
the special base
name:
base.CallOnTheBase();
Using that, we can reimplement our TraineeFirefighter
and remove that duplicate
code, as shown in Example 4-12.
Example 4-12. Avoiding duplication by calling the base class
class TraineeFirefighter : Firefighter { private bool hoseTrainedOnFire; protected override void TurnOnHose() { if (hoseTrainedOnFire) { // Call on the base implementation base.TurnOnHose(); } else { Console.WriteLine("There's water going everywhere!"); } } protected override void TrainHoseOnFire() { hoseTrainedOnFire = true; base.TrainHoseOnFire(); } }
So, what happens if in a later version we change the implementation
of the ExtinguishFire
method on the
base class? Maybe we found an optimization that means it is faster to
implement it like this:
public virtual void ExtinguishFire() { Console.WriteLine("{0} is putting out the fire!", Name); // We've swapped these around TurnOnHose(); TrainHoseOnFire(); }
Let’s imagine that this Firefighter
class is being implemented by one of
our colleagues. She tested the new implementation against her Firefighter
unit test suite, exactly as
required, and everything passed just fine—fires were extinguished. Then
she handed it over to us to use (with our new TraineeFirefighter
class that we’re working
on).
If we compile and run, we get the following output:
Joe is putting out the fire! Training the hose on the fire. The fire is going out. Bill is putting out the fire! There's water going everywhere! Training the hose on the fire.
So the Firefighter
code works
fine, just as our colleague promised; but our TraineeFirefighter
has made a bit of a mess.
This is a shame, as he has not done anything different—we didn’t change a
single line of our TraineeFirefighter
code that was working just a moment earlier.
The problem is that, while our documentation for ExtinguishFire
told us that it would call both
of those virtual
methods it didn’t
promise to do so in any particular order. And there was no documentation
at all on our protected virtual
methods
to tell us how we should override them, or whether
there were any particular constraints or invariants we should
maintain.
Warning
This is a very common combination of problems when designing an
inheritance hierarchy—poor documentation on the base class, and
insufficiently defensive implementations in a derived class. Creating a
class hierarchy is not an easy thing to do. And this is when we’re only
making selected methods virtual
—imagine the chaos if all methods were
virtual
by default!
In the next chapter, we’re going to look at some alternative ways to vary behavior that are more easily documented and potentially more robust than deriving from a base class.
That’s not to say that you shouldn’t make use of such a powerful concept as polymorphism; it is just that you should take care when you do so.
Let’s just recap the implications of all that, as it is a bit complicated.
Back in Chapter 3, when we designed our first class, we talked about its public contract, and how that encapsulated the implementation details which allowed us to use it as though it was a black box.
With the addition of public
and
protected virtual
members, we’re
opening that black box and creating a second contract: for people who
derive their own classes, which, as we just saw, is a whole lot more
complex.
The designers of C# decided that should be an opt-in complexity:
unless we specify that a member is virtual
we don’t have to worry about it. Along
the same lines, they’ve also provided a way to ensure that we don’t have
to worry about anyone deriving from us at all.
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.
An abstract base class is intended to provide the “scaffolding” for a hierarchy of related classes, but it is not intended to be instantiated itself, because it isn’t “finished.” It requires that classes derived from it add in some missing bits.
Let’s turn our current firefighter into an abstract base for the others to use, and see how that works.
First, we can add the abstract
modifier to the class itself, and see what happens:
abstract class Firefighter { // ... }
As usual, we add the modifier before the class
keyword (and after any accessibility
modifiers, if present).
If we build, we now get a compiler error:
Cannot create an instance of the abstract class or interface 'Firefighter'
That’s because we’re trying to create an instance of the Firefighter
in our main function:
Firefighter joe = new Firefighter { Name = "Joe" };
This is no longer allowed, because the Firefighter
class is now abstract.
OK, we’ll comment that out temporarily while we carry on refactoring. We want it to continue to build as we go so that we can see if we’ve introduced any other errors:
//Firefighter joe = new Firefighter { Name = "Joe" }; //joe.ExtinguishFire;
Build and run, and we get the output we expect—Bill is still spraying the water around:
Bill is putting out the fire! There's water going everywhere! Training the hose on the fire.
One other thing: if we’re creating an abstract base class, we
usually name it something such as FooBase
to distinguish it from a regular class.
This is by no means a hard-and-fast rule, but it is pretty common. So
let’s rename Firefighter
to FirefighterBase
, and make sure we change it
where it is referenced elsewhere—on the Firetruck
, FireChief
, and TraineeFirefighter
classes.
The easiest way to do that is to use the automatic rename refactoring in the IDE. Just type over the old name in the declaration, click on the Smart Tag that appears, and choose Rename Firefighter to FirefighterBase from the menu. You could do it by hand if you wanted, though.
The whole purpose of this was to get rid of the default
implementation we have for putting out fires, so let’s turn Firefighterbase.ExtinguishFire
into an
abstract method.
Just like the modifier for the class, we use the abstract
keyword, but this time we also remove
the method body and add a semicolon at the end of the declaration:
abstract class FirefighterBase { public abstract void ExtinguishFire(); }
If you try building again now, you can see that we have a new compiler error:
'TraineeFirefighter' does not implement inherited abstract member 'FirefighterBase.ExtinguishFire()'
Remember, we are required to override an
abstract
method; our class isn’t
finished until we do so (unlike a virtual
method, where we are invited to override
it, but it will fall back on the base if we don’t). While our FireChief
does override the method, our TraineeFirefighter
doesn’t. So we need to add a
suitable implementation:
class TraineeFirefighter : FirefighterBase
{
// Override the abstract method
public override void ExtinguishFire()
{
// What are we going to put here?
}
// ...
}
But what are we going to put into that ExtinguishFire
override? Before, we depended on
our base class for the implementation, but our base is now abstract, so we
don’t have one available anymore!
That’s because we’ve forgotten about our regular Firefighter
. Let’s add a class for him back into
the hierarchy:
class Firefighter : FirefighterBase
{
public override void ExtinguishFire()
{
Console.WriteLine("{0} is putting out the fire!", Name);
TurnOnHose();
TrainHoseOnFire();
}
}
Notice we’ve given him the “standard firefighter” implementation for
ExtinguishFire
.
If we take one more look at the base class, we can see that we still
have those two virtual
implementation
helpers. While everything builds correctly at the moment, they don’t
really belong there; they are really a part of the Firefighter
implementation, so let’s move them
in there. We end up with the code in Example 4-13.
Example 4-13. Refactored base classes
abstract class FirefighterBase { public abstract void ExtinguishFire(); public string Name { get; set; } public void Drive(Firetruck truckToDrive, Point coordinates) { if (truckToDrive.Driver != this) { // We can't drive the truck if we're not the driver return; } truckToDrive.Drive(coordinates); } } class Firefighter : FirefighterBase { public override void ExtinguishFire() { Console.WriteLine("{0} is putting out the fire!", Name); TrainHoseOnFire(); TurnOnHose(); } protected virtual void TurnOnHose() { Console.WriteLine("The fire is going out.");
} protected virtual void TrainHoseOnFire() { Console.WriteLine("Training the hose on the fire.");
} }
But we’re still not quite done! If you build this you’ll see another compiler error:
'TraineeFirefighter.TurnOnHose()': no suitable method found to override 'TraineeFirefighter.TrainHoseOnFire()': no suitable method found to override
Our trainee firefighter really is a kind of firefighter, and depends
on those two virtual
functions we just moved. The error
message is telling us that we can’t override a method that isn’t actually
present in the base.
We need to change its base class from FirefighterBase
to Firefighter
. This has the advantage that we can
also get rid of its duplicate override of the ExtingushFire
method (see Example 4-14).
Example 4-14. Using the newly refactored base classes
class TraineeFirefighter : Firefighter { protected override void TurnOnHose() { if (hoseTrainedOnFire) { Console.WriteLine("The fire is going out."); } else { Console.WriteLine("There's water going everywhere!"); } } private bool hoseTrainedOnFire; protected override void TrainHoseOnFire() { hoseTrainedOnFire = true; Console.WriteLine("Training the hose on the fire."); } }
We also need to uncomment our two lines about Joe in the Main
function—everything should work
again:
Firefighter joe = new Firefighter { Name = "Joe" }; joe.ExtinguishFire();
We can build and run to check that. We get the expected output:
Joe is putting out the fire! Training the hose on the fire. The fire is going out. Bill is putting out the fire! There's water going everywhere! Training the hose on the fire.
Let’s remind ourselves of the current class hierarchy (see Figure 4-2). Our FireChief
is no longer an “ordinary” Firefighter
, with an override for putting out
fires, but he does take advantage of our common scaffolding for
“firefighters in general” that we modeled as an abstract base class called
FirefighterBase
. Our Firefighter
also takes advantage of that same
scaffolding, but our TraineeFirefighter
really is a Firefighter
—just with its
own idiosyncratic way of doing some of the internal methods that Firefighter
uses to get the job done.
Back to the requirements for our fire department application: let’s say we want to keep track of who is actually in the fire station at any particular time, just in case there is a fire on the premises and we can take a roll call (health and safety is very important, especially in a fire station).
There are two types of folks in the fire station: the firefighters
and the administrators. Example 4-15 shows our new
Administrator
class.
Example 4-15. A class representing administrative staff
class Administrator { public string Title { get; set; } public string Forename { get; set; } public string Surname { get; set; } public stringName
{ get { StringBuildername =
new StringBuilder();
AppendWithSpace(name, Title); AppendWithSpace(name, Forename); AppendWithSpace(name, Surname); return name.ToString(); } } void AppendWithSpace(StringBuilder builder, string stringToAppend) { // Don't do anything if the string is empty if (string.IsNullOrEmpty(stringToAppend)) { return; } // Add a space if we've got any text already if (builder.Length > 0) { builder.Append(" "); } builder.Append(stringToAppend); } }
If you look at our Firefighter
class, it had a single string
property
for a Name
. With the Administrator
, you can independently get and set
the Title
, Forename
, and Surname
. We then provided a special read-only
property that returns a single formatted string for the whole Name
. It uses a framework class called StringBuilder
to assemble the name from the
individual components as efficiently as possible.
AppendWithSpace
is a utility
function that does the actual work of concatenating the substrings. It
works out whether it needs to append anything at all using a static
method on string
that checks whether it is null or empty,
called IsNullOrEmpty
; finally, it adds
an extra space to separate the individual words.
To do the roll call we want to write some code such as that in Example 4-16.
Example 4-16. Using the Administrator class
static void Main(string[] args) { FireStation station = new FireStation(); // A reference to Joe, Harry's number one Firefighter joe = new Firefighter { Name = "Joe" }; // A reference to Bill, the trainee FirefighterBase bill = new TraineeFirefighter { Name = "Bill" }; // Harry is back FireChief bigChiefHarry = new FireChief { Name = "Harry"}; // And here's our administrator - Arthur Administrator arthur = new Administrator { Title = "Mr", Forename = "Arthur", Surname = "Askey" }; station.ClockIn(joe); station.ClockIn(bill); station.ClockIn(bigChiefHarry); station.ClockIn(arthur); station.RollCall(); Console.ReadKey(); }
Note
When you are designing a class framework it can often be a good idea to write some example client code. You can then ensure that your design is a good abstract model while supporting clean, simple code at point-of-use.
Clearly, we’re going to need a FireStation
class that is going to let our
administrators and firefighters ClockIn
(registering their presence in the station), and where we can do a
RollCall
(displaying their names). But
what type is that ClockIn
function
going to take, given that we haven’t specified any common base class that
they share?
.NET comes to our rescue again. It turns out that
every type in the system is derived from Object
. Every one—value types (struct
) and reference types (class
) alike, even the built-in types such as
Int32
.
It is easy to see how that would work for a class declaration in C#.
If you don’t specify a particular base class, you get Object
by default.
But what about a struct
, or
enum
, or the built-in types; what
happens if we try to talk to them through their Object
“base class”?
Let’s give it a try. This code snippet will compile and work quite happily:
// Int variable int myIntVariable = 1; object myObject = myIntVariable;
What happens under the covers is that the runtime allocates a new object and puts a copy of the value inside it. This is called boxing, and, as you might expect given that it involves allocating objects and copying values, it is relatively expensive when compared to a straightforward assignment.
You can also convert back the other way:
// Int variable int myIntVariable = 1; object myObject = myIntVariable; int anotherIntVariable = (int)myObject;
Notice how we use the type name in parentheses to perform the
conversion back to an int
. In
general, this sort of conversion from one type to another is known as a
“cast,” and will work for classes too (although we’ll see a more
explicit way of doing that later in this chapter).
The runtime looks at that box object for us and checks that it contains a value of the correct type. If so, it will copy the value back out of the box and into the new variable.
Note
What if it isn’t of the correct type? The
runtime will throw an InvalidCastException
. You can find out more
about exceptions in Chapter 6.
That process is known as unboxing, and is also quite expensive (although not as expensive as boxing, as it doesn’t need to allocate the object).
Although these performance costs are individually fairly small, if you are processing large numbers of value types in a way that requires them to be repeatedly boxed and unboxed the costs can add up quite rapidly; so you should be aware of boxing and unboxing when you are profiling your application.
So the only common base of both Firefighter
and Administrator
is Object
at the moment (remember, everything is
ultimately derived from Object
). That
seems a bit low level, but it is all we have to go on for now, so we’ll
make do.
Example 4-17 shows our first pass at a
FireStation
.
Example 4-17. FireStation class
class FireStation { List<object> clockedInStaff = new List<object>(); public void ClockIn(object staffMember) { if (!clockedInStaff.Contains(staffMember)) { clockedInStaff.Add(staffMember); } } public void RollCall() { foreach(object staffMember in clockedInStaff) { // Hmmm... What to do? } } }
Our ClockIn
method is making
use of a list of objects to keep track of who is in the station. To do
that it is using the generic collection class List<T>
we first
saw in Chapter 2. Using the
List.Contains
method, the
implementation checks that they weren’t already in the station, and adds
them if necessary.
Everything is fine so far. Then we reach the RollCall
method. We’re using foreach
to iterate over the clocked-in staff,
but we don’t actually have a method to call to get their names!
We want a way of indicating that these disparate object types (firefighters, fire chiefs, and administrators) all support giving out their name.
We saw one way of doing that already: we could create a common
base class, and move the Name
functionality in there. Let’s see what happens if we try to do
that.
Practically speaking, we have two completely different
implementations of the Name
property.
We saw that we can model that situation with an abstract base class from
which Firefighter
and Administrator
both derive, both implementing
the method in their own way.
Here’s our NamedPerson
base
with an abstract property for the Name
:
abstract class NamedPerson { public abstract string Name { get; } }
There’s no problem when we implement this on our Administrator
:
class Administrator : NamedPerson { public override stringName
{ get { StringBuildername =
new StringBuilder();
AppendWithSpace(name, Title); AppendWithSpace(name, Forename); AppendWithSpace(name, Surname); return name.ToString(); } } // ... }
Notice how we derived from NamedPerson
and added the override
modifier to our Name
property so that it overrides the
abstract method in our base.
That’s fine so far. What about our FirefighterBase
? Let’s try doing exactly the
same thing:
abstract class FirefighterBase : NamedPerson { public abstract void ExtinguishFire(); public override stringName {
get; set;}
// ... }
If we compile that, we get an error:
'FirefighterBase.Name.set': cannot override because 'NamedPerson.Name' does not have an overridable set accessor
We run into difficulty because FirefighterBase
has both
a getter and a setter for the Name
property, but our base allows only a getter.
Well, we could work around that with another member to set the name; but as you can see in Example 4-18, it is all getting a bit ugly.
Example 4-18. Mounting evidence that all is not well in our class hierarchy
abstract class FirefighterBase : NamedPerson
{
public abstract void ExtinguishFire();
public override string Name
{
get
{
return RealName;
}
}
public string RealName
{
get; set;
}
// ...
}
Not only is it ugly, but we have to replace all our object
initializers to refer to our new RealName
property, so it is making us do
unnecessary work, which is never good:
Firefighter joe = new Firefighter { RealName = "Joe" };
Are you feeling uncomfortable with this approach yet? Let’s push
on with it just a little bit further, and see what happens if we want to
support a second behavior. Say we had a SalariedPerson
abstract base that provides us
with the contract for getting/setting a person’s salary. We’re going to
need to apply that to both the FirefighterBase
and the Administrator
, to tie in with the billing
system:
abstract class SalariedPerson
{
public abstract decimal Salary
{
get;
set;
}
}
We’re providing a decimal
property for the Salary
that must be
implemented by any SalariedPerson
.
So, what happens if we now try to derive from this class for our
Administrator
, as shown in Example 4-19?
Example 4-19. The final straw: Our class hierarchy needs a rethink
class Administrator : NamedPerson, SalariedPerson
{ private decimalsalary;
public override decimalSalary
{ get { return salary; } set { salary = value; } } // ... }
Note
C++ developers will be familiar with this syntax for specifying multiple base classes.
Class 'Administrator' cannot have multiple base classes: 'NamedPerson' and 'SalariedPerson'
This is a pretty fundamental roadblock! You cannot derive your class from more than one base class.
When the designers of .NET were thinking about the platform fundamentals, they looked at this issue of multiple inheritance and how they’d support it across multiple languages, including C#, VB, and C++. They decided that the C++ approach was too messy and prone to error (particularly when you think about how to resolve members that appear in both base classes with the same signature). The implications of multiple inheritance were probably just too difficult to come to grips with, and therefore were unlikely to bring net productivity gains. With that view prevailing, single inheritance of implementation is baked into the platform.
Note
In more recent interviews, the .NET team has reflected that perhaps there might have been a way of allowing multiple inheritance of implementation, without introducing all the complexity of C++ multiple inheritance. That’s the benefit of 20/20 hindsight; we (or our children) will just have to wait until the next platform generation and see how the argument goes then.
So are we really stymied? No! While we can’t support multiple inheritance of implementation, we can support multiple inheritance of interface.
With our abstract base classes in this example, we’re not really trying to provide a base implementation for our objects at all. We’re trying to mark them as supporting a particular contract that we can then rely on in our client code.
C# provides us with a mechanism for doing exactly that: interface
. Let’s rewrite our NamedPerson
and SalariedPerson
classes as interfaces instead, as
shown in Example 4-20.
Example 4-20. Defining interfaces
interface INamedPerson { string Name { get; } } interface ISalariedPerson { decimal Salary { get; set; } }
We use much the same syntax as we do for a class definition, but
using the keyword interface
instead.
Notice also that we dropped the abstract modifier on the members; an
interface is implicitly without implementation. There are no accessibility
modifiers on the members either; an interface member is only ever allowed
to be public
.
The only other change we’ve made is to prefix our interface name
with an I
. This is not a rule, but
another one of those naming conventions to which most people
conform.
We can now implement those interfaces on our Administrator
, as shown in Example 4-21.
Example 4-21. Implementing interfaces
class Administrator : INamedPerson, ISalariedPerson { public decimal Salary { get; set; } public string Name { get { StringBuilder name = new StringBuilder(); AppendWithSpace(name, Title); AppendWithSpace(name, Forename); AppendWithSpace(name, Surname); return name.ToString(); } } // ... }
And we can implement them on our FirefighterBase
, as shown in Example 4-22.
Example 4-22. The same interfaces in a different part of the class hierarchy
abstract class FirefighterBase : INamedPerson, ISalariedPerson { public string Name { get; set; } public decimal Salary { get; set; } // ... }
Notice that we can happily implement the setter on our FirefighterBase
, even though the interface only
requires a getter. The restrictions on how you
implement the interface—as long as
you conform to the contract it specifies—are much looser than those on
overrides of a base class. Also, C# doesn’t allow you to use the simple
property syntax to define virtual properties or their overrides, but there
is no such restriction when you’re implementing an interface. So we’ve
been able to use simple property syntax here rather than having to
implement using full-blown properties.
We can now make use of this interface in our FireStation
class. Instead of a list of objects,
we can use a list of INamedPerson
, and
call on the Name
property in our
RollCall
method,
as shown in Example 4-23.
Example 4-23. Modifying the FireStation class to use an interface
class FireStation { List<INamedPerson> clockedInStaff = new List<INamedPerson>(); public void ClockIn(INamedPerson staffMember) { if (!clockedInStaff.Contains(staffMember)) { clockedInStaff.Add(staffMember); Console.WriteLine("Clocked in {0}", staffMember.Name); } } public void RollCall() { foreach (INamedPerson staffMember in clockedInStaff) { Console.WriteLine(staffMember.Name); } } }
Note
If you’ve been following through the code in Visual Studio (which I thoroughly recommend), you’ll also need to change your object initializers back to this form:
Firefighterjoe =
new Firefighter{ Name =
"Joe"};
If we compile and run, we get the output we hoped for—a roll call of everyone in the station:
Clocked in Joe Clocked in Bill Clocked in Harry Clocked in Mr Arthur Askey Joe Bill Harry Mr Arthur Askey
Interfaces support inheritance too, just like classes. If you want, you could create a named, salaried person interface like this:
interface INamedSalariedPerson : INamedPerson, ISalariedPerson { }
What happens if you have conflicting names? Imagine the interface
ISettableNamed
Person
:
interface ISettableNamedPerson { string Name { get; set; } }
What happens if we implement both INamedPerson
and ISettableNamedPerson
on our FirefighterBase
?
abstract class FirefighterBase : INamedPerson, ISettableNamedPerson, ISalariedPerson { // ... }
The answer is that everything is just fine! Each interface requires
that we implement a string
property
called Name
; one requires at least a
getter, the other a getter and a setter.
When we access the property through the relevant interface, it can resolve correctly which member we meant; there’s no requirement for a separate implementation for each interface.
But what if that was actually wrong? What if our Name
property on INamedPerson
had entirely different semantics
from the one on ISettableNamedPerson
?
Let’s suppose that one is intended to allow only letters and numbers with
no spaces and the other is just our freeform “any old text” implementation
with which we are familiar.
Whenever our client expects an INamedPerson
we need to provide the second
implementation, and whenever the client expects an ISettableNamedPerson
, the first.
We can do that by explicitly implementing the interfaces.
To explicitly implement a particular member of an interface, you drop the accessibility modifier and add the interface name as a prefix, as shown in Example 4-24.
Example 4-24. Explicit interface implementation
class AFootInBothCamps : INamedPerson, ISettableNamedPerson
{
private string settableName;
string INamedPerson.Name
{
get
{
Console.WriteLine("Accessed through the INamedPerson interface");
return settableName;
}
}
string ISettableNamedPerson.Name
{
get
{
return settableName;
}
set
{
Console.WriteLine(
"Accessed through the " +
"ISettableNamedPerson interface");
if( settableName != null && settableName.Contains(" ") )
{
// You can't set it if it contains the space
// character
return;
}
settableName = value;
}
}
}
Example 4-25 shows how we’re going to access them from our main function.
Example 4-25. Calling different interface implementations of the same member name on the same object
class Program { static void Main(string[] args) { AFootInBothCampsboth =
new AFootInBothCamps(); ISettableNamedPerson settablePerson = both; INamedPerson namedPerson = both; settablePerson.Name = "hello"; Console.WriteLine(settablePerson.Name
); Console.WriteLine(namedPerson.Name
); Console.ReadKey(); } }
Notice how we’re creating our object, and then providing two
additional references to it: one through a variable of type ISettableNamedPerson
and one through INamedPerson
.
We then call on the Name
property through each of those interfaces, and get the following
output:
Accessed through the ISettableNamedPerson interface hello Accessed through the INamedPerson interface hello
But what if we try to access it through a reference typed to the class itself?
Console.WriteLine(both.Name);
Add the following line to the main function and compile, and we get a compiler error!
'AFootInBothCamps' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'AFootInBothCamps' could be found (are you missing a using directive or an assembly reference?)
We’ve seen that error before; it means we’re trying to talk to a member that doesn’t exist. What’s happened is that the members that are explicitly implemented exist only if we are accessing them through the relevant interfaces.
However, as long as we explicitly implement one of the two (or two of the three, or however many we’re stuck with), we can choose one interface as our “default” and implement it using the regular syntax, as shown in Example 4-26.
Example 4-26. Implementing one of the interfaces implicitly
class AFootInBothCamps : INamedPerson, ISettableNamedPerson { private string settableName; // Regular implementation syntax public stringName
{ get { Console.WriteLine("Accessed through the INamedPerson interface");
return settableName; } } string ISettableNamedPerson.Name { get { return settableName; } set { Console.WriteLine("Accessed through the ISettableNamedPerson " + "interface"); if( settableName != null && settableName.Contains(" ") ) { // You can't set it if it contains the space // character return; } settableName = value; } } }
Now we can compile and run, and the default implementation for our
class is the one for the INamedPerson
interface:
Accessed through the ISettableNamedPerson interface hello Accessed through the INamedPerson interface hello Accessed through the INamedPerson interface hello
Note
In real life, you don’t often come across this need for explicit interface implementation. If you have control over all the code in the application, you should avoid designing in a clash where the names are the same but the semantics are different. Like overloads or overrides with different meanings, it surprises other developers.
The .NET Framework contains a few examples where it uses explicit interface implementation to hide the interface members from the public API of a class, even though there is no clash. The authors are unconvinced that this improves matters.
More often, you will come across this usage where you don’t have control of the code—with two third-party libraries, for instance, both of which declare interfaces with different semantics but a clash of names. Even then, this is not a problem unless you happen to need to implement both interfaces on one class. Even rarer!
Right, let’s go back to our FireStation
class for a minute, and imagine an
interface we could create to formalize the contract for clocking in: our
billing system might define this contract for us so that we can plug
into it.
As it happens, our FireStation
provides an implementation which can ClockIn
named people, but our billing system’s
IClockIn
contract is much more
generic—it can clock in anything of type Object
, as we had in our original
implementation:
interface IClockIn { void ClockIn(object item); }
We can now implement IClockIn
on our FireStation
, as shown in Example 4-27.
Example 4-27. Implementing the IClockIn interface
class FireStation : IClockIn
{
List<INamedPerson> clockedInStaff = new List<INamedPerson>();
public void ClockIn(INamedPerson staffMember)
{
if (!clockedInStaff.Contains(staffMember))
{
clockedInStaff.Add(staffMember);
Console.WriteLine("Clocked in {0}", staffMember.Name);
}
}
public void RollCall()
{
foreach (INamedPerson staffMember in clockedInStaff)
{
Console.WriteLine(staffMember.Name);
}
}
public void ClockIn(object item)
{
// What to do here
}
}
Our original ClockIn
method is
unchanged, and we’ve added a new overload that takes an object, and
therefore matches the requirement in our interface. But how do we
implement that new method? We want to check that the person being
clocked in is an INamedPerson
, and if
it is, perform our usual operation. Otherwise, we want to tell the user
that we can’t clock him in.
In other words, we need a manual check for the type of the object.
C# provides us with a couple of keywords for checking the
type of an object: as
and is
.
Here’s how we can use them in our ClockIn
implementation:
public void ClockIn(object item) { if(item is INamedPerson)
{ ClockIn(item as INamedPerson
); } else { Console.WriteLine("We can't check in a '{0}'", item.GetType());
} }
Notice how we are using the type name to check if the item is
of that type. And then we call our
other overload of ClockIn
by
explicitly converting to a reference of our INamedPerson
type, using as
.
It checks to see if our object would be accessible through a reference of the specified type. It looks at the whole inheritance hierarchy for the object (up and down) to see if it matches, and if it does, it provides us a reference of the relevant type.
What if you don’t bother with the is
check and just use as
? Conveniently, the as
operation just converts to a null
reference if it can’t find a suitable type
match:
public void ClockIn(object item) { INamedPerson namedPerson = item as INamedPerson; if(namedPerson != null) { ClockIn(namedPerson); } else { Console.WriteLine("We can't check in a '{0}'", item.GetType()); } }
This is the form in which you most often see a test like this,
because it is marginally more efficient than the previous example. In the
first version, the runtime has to perform the expensive runtime type
checking twice: once for the if()
statement and once to see whether we can actually perform the conversion,
or whether null
is required. In the
second case, we do the expensive check only once, and then do a simple
test for null
.
So far, we’ve seen how to create classes; to model relationships
between instances of those classes through association, composition, and
aggregation; and to create relationships between classes by derivation. We
also saw how virtual
functions enable
derived classes to replace selected aspects of a base class.
We saw how to use protected
and
protected internal
to control the
visibility of members to derived classes. Then, we saw how we can use
either abstract
classes and methods or
interfaces to define public contracts for a class.
Finally, we looked at a means of examining the inheritance hierarchy by hand, and verifying whether an object we are referencing through a base class is, in fact, an instance of a more derived class.
In the next chapter, we are going to look at some other techniques for code reuse and extensibility that don’t rely on inheritance.
Get Programming C# 4.0, 6th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.