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.
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.
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.
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.
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.
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.