Inheritance and Polymorphism—Creating Polymorphic Types
| Visual C# Tutorials |
| C# Tutorials |
| © 2006 O'Reilly Media, Inc. |
Creating Polymorphic Types
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 ListBoxes and which are Buttons; 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:
1. Create a base class with virtual methods.
2. 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:
public virtual 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 public virtual 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 public override 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}\n", 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( ):
public override 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.
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.
|

