There are two powerful aspects to inheritance. One is code reuse.
When you create a ListBox
class,
you’re able to reuse some of the logic in the base (Window)
class.
What is arguably more powerful, however, is the second aspect of inheritance: polymorphism . Poly means many and morph means form. Thus, polymorphism refers to being able to use many forms of a type without regard to the details.
When the phone company sends your phone a ring signal, it does not know what type of phone is on the other end of the line. You might have an old-fashioned Western Electric phone that energizes a motor to ring a bell, or you might have an electronic phone that plays digital music.
As far as the phone company is concerned, it knows only about the “base type” phone and expects that any “derived” instance of this type knows how to ring. When the phone company tells your phone to ring, it, effectively, calls your phone’s ring method, and old fashioned phones ring, digital phones trill, and cutting-edge phones announce your name. The phone company doesn’t know or care what your individual phone does; it treats your telephone polymorphically.
Because a ListBox
is a Window
and a Button
is
a Window
, you expect to
be able to use either of these types in situations that call for a
Window. For example, a form might want to keep a collection of all the
derived instances of Window it manages (buttons, lists, and so on), so
that when the form is opened, it can tell each of its Windows to draw
itself. For this operation, the form does not want to know which
elements are ListBox
es and which
are Button
s; it just wants to tick
through its collection and tell each one to “draw.” In short, the form
wants to treat all its Window objects polymorphically.
You implement polymorphism in two steps:
Create a base class with virtual methods.
Create derived classes that override the behavior of the base class’s virtual methods.
To create a method in a base class that supports polymorphism,
mark the method as virtual
. For
example, to indicate that the method DrawWindow( )
of class Window in Example 11-1 is polymorphic,
add the keyword virtual
to its
declaration, as follows:
publicvirtual
void DrawWindow( )
Each derived class is free to inherit and use the base class’s
DrawWindow( )
method as is or to
implement its own version of DrawWindow( )
. If a derived class does override the DrawWindow( )
method, that overridden
version will be invoked for each instance of the derived class. You
override the base class virtual method by using the keyword override
in the derived class method
definition, and then add the modified code for that overridden
method.
Example 11-2 shows how to override virtual methods .
Example 11-2. Virtual methods
using System; public class Window { // constructor takes two integers to // fix location on the console public Window( int top, int left ) { this.top = top; this.left = left; } // simulates drawing the window publicvirtual
void DrawWindow( ) { Console.WriteLine( "Window: drawing Window at {0}, {1}", top, left ); } // these members are protected and thus visible // to derived class methods. We'll examine this // later in the chapter. (Typically, these would be private // and wrapped in protected properties, but the current approach // keeps the example simpler.) protected int top; protected int left; } // end Window // ListBox derives from Window public class ListBox: Window
{ // constructor adds a parameter // and calls the base constructor public ListBox( int top, int left,string contents
) : base( top, left ) { listBoxContents = contents; } // an overridden version (note keyword) because in the // derived method we change the behavior publicoverride
void DrawWindow( ) { base.DrawWindow( ); // invoke the base method Console.WriteLine( "Writing string to the listbox: {0}", listBoxContents ); } private string listBoxContents; // new member variable } // end ListBox public class Button: Window
{ public Button( int top, int left ) : base( top, left ) {} // an overridden version (note keyword) because in the // derived method we change the behavior public override void DrawWindow( ) { Console.WriteLine( "Drawing a button at {0}, {1} ", top, left ); } } // end Button public class Tester { static void Main( ) { Window win = new Window( 1, 2 ); ListBox lb = new ListBox( 3, 4, "Stand alone list box" ); Button b = new Button( 5, 6 ); win.DrawWindow( ); lb.DrawWindow( ); b.DrawWindow( ); Window[] winArray = new Window[3]; winArray[0] = new Window( 1, 2 ); winArray[1] = new ListBox( 3, 4, "List box in array" ); winArray[2] = new Button( 5, 6 ); for ( int i = 0; i < 3; i++ ) { winArray[i].DrawWindow( ); } // end for } // end Main } // end Tester
The output looks like this:
Window: drawing Window at 1, 2 Window: drawing Window at 3, 4 Writing string to the listbox: Stand alone list box Drawing a button at 5, 6 Window: drawing Window at 1, 2 Window: drawing Window at 3, 4 Writing string to the listbox: List box in array Drawing a button at 5, 6
In Example 11-2,
ListBox
derives from Window and
implements its own version of DrawWindow( )
:
publicoverride
void DrawWindow( )
{
base.DrawWindow( ); // invoke the base method
Console.WriteLine ("Writing string to the listbox: {0}",
listBoxContents);
}
The keyword override
tells
the compiler that this class has intentionally overridden how DrawWindow( )
works. Similarly, you’ll
override DrawWindow( )
in another
class that derives from Window
: the
Button
class.
In the body of the example, you create three objects: a Window
, a ListBox
, and a Button
. Then you call DrawWindow( )
on each:
Window win = new Window(1,2); ListBox lb = new ListBox(3,4,"Stand alone list box"); Button b = new Button(5,6); win.DrawWindow( ); lb.DrawWindow( ); b.DrawWindow( );
This works much as you might expect. The correct DrawWindow( )
method is called for each. So
far, nothing polymorphic has been done (after
all, you called the Button
version
of DrawWindow
on a Button
object). The real magic starts when
you create an array of Window
objects.
Because a ListBox
is a Window, you are free to place a ListBox
into an array of Windows
. Similarly, you can add a Button
to a collection of Windows
, because a Button
is a Window
.
Window[] winArray = new Window[3]; winArray[0] = new Window(1,2); winArray[1] = new ListBox(3,4,"List box in array"); winArray[2] = new Button(5,6);
The first line of code declares an array named winArray
that will hold three Window
objects. The next three lines add new
Window
objects to the array. The
first adds an object of type Window
. The second adds an object of type
ListBox
(which is a Window
because ListBox
derives from Window
), and the third adds an object of
type Button
, which is also a type
of Window
.
What happens when you call DrawWindow( )
on each of these objects?
for (int i = 0; i < winArray.Length-1; i++) { winArray[i].DrawWindow(); }
This code uses i
as a counter
variable. It calls DrawWindow( )
on
each element in the array in turn. The value i
is evaluated each time through the loop,
and that value is used as an index into the array.
All the compiler knows is that it has three Window
objects and that you’ve called
DrawWindow( )
on each. If you had
not marked DrawWindow( )
as
virtual
, Window
’s original DrawWindow( )
method would be called three
times.
However, because you did mark DrawWindow( )
as virtual, and because the
derived classes override that method, when you call DrawWindow( )
on the array, the right thing
happens for each object in the array. Specifically, the compiler
determines the runtime type of the actual objects (a Window
, a ListBox
, and a Button
) and calls the right method on each.
This is the essence of polymorphism.
The runtime type of an object is the actual (derived) type. At
compile time, you do not have to decide what kind of objects will be
added to your collection, so long as they all derive from the
declared type (in this case, Window
). At runtime, the actual type is
discovered and the right method is called. This allows you to pick
the actual type of objects to add to the collection while the
program is running.
Note that throughout this example, the overridden methods are
marked with the keyword override
:
public override void DrawWindow( )
The compiler now knows to use the overridden method when
treating these objects polymorphically. The compiler is responsible
for tracking the real type of the object and for handling the late
binding, so that ListBox.DrawWindow( )
is called when the Window reference really points to a
ListBox
object.
In C#, the programmer’s decision to override a virtual
method is made explicit with the override
keyword. This helps you release new
versions of your code; changes to the base class will not break
existing code in the derived classes. The requirement to use the
override
keyword helps prevent that
problem.
Here’s how: assume for a moment that Company A wrote the
Window
base class in Example 11-2. Suppose also
that the ListBox
and RadioButton
classes were written by
programmers from Company B using a purchased copy of the Company A
Window class as a base. The programmers in Company B have little or no
control over the design of the Window class, including future changes
that Company A might choose to make.
Now suppose that one of the programmers for Company B decides to
add a Sort( )
method to ListBox
:
public class ListBox : Window { public virtual void Sort( ) {...} }
This presents no problems until Company A, the author of
Window
, releases Version 2 of its
Window
class, and the programmers
in Company A also add a Sort( )
method to their public class Window
:
public class Window { // ... public virtual void Sort( ) {...} }
In other object-oriented languages (such as C++), the new
virtual Sort( )
method in Window
would now act as a base virtual
method for the Sort( )
method in
ListBox
, which is not what the
developer of ListBox
intended.
C# prevents this confusion. In C#, a virtual
function is always considered to be
the root of virtual dispatch ; that is, once C# finds a virtual method,
it looks no further up the inheritance hierarchy. If a new virtual
Sort( )
function is introduced into
Window
, the runtime behavior of
ListBox
is unchanged.
When ListBox
is compiled
again, however, the compiler generates a
warning:
...class1.cs(54,24): warning CS0114: 'ListBox.Sort( )' hides inherited member 'Window.Sort( )'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
Never ignore warnings. Treat them as errors until you have satisfied yourself that you understand the warning and that it is not only innocuous but that there is nothing you can do to eliminate the warning. Your goal, (almost) always, is to compile warning-free code.
To remove the warning, the programmer must indicate what she
intends.[8] She can mark the ListBox Sort( )
method new
to indicate
that it is not an override of the virtual method
in Window:
public class ListBox : Window { public new virtual void Sort( ) {...}
This action removes the warning. If, on the other hand, the
programmer does want to override the method in Window, she need only
use the override
keyword to make
that intention explicit:
public class ListBox : Window { public override void Sort( ) {...}
To avoid this warning, it might be tempting to add the
new
keyword to all your virtual
methods. This is a bad idea. When new
appears in the code, it ought to
document the versioning of code. It points a potential client to the
base class to see what it is that you are intentionally not
overriding. Using new
scattershot
undermines this documentation and reduces the utility of a warning
that exists to help identify a real issue.
If the programmer now creates any new classes that derive from
ListBox
, those derived classes will
inherit the Sort( )
method from
ListBox
, not from the base Window
class.
[8] In standard English, one uses “he” when the pronoun might refer either to a male or a female. Nonetheless, this assumption has such profound cultural implications, especially in the male-dominated programming profession, that I will use the term “she” for the unknown programmer from time to time. I apologize if this causes you to falter a bit when reading; consider it an opportunity to reflect on the linguistic implications of a patriarchal society.