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 (Control
) 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 Control
and a Button
is a Control
, you expect to be able to use either of these types in situations that call for a Control
. For example, a form might want to keep a collection of all the derived instances of Control
it manages (buttons, lists, and so on) so that when the form is opened, it can tell each of its Control
s 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 Control
objects polymorphically.
You implement polymorphism in two steps:
To create a method in a base class that supports polymorphism, you mark the method as virtual
. For example, to indicate that the method DrawControl( )
of class Control
in Example 11-1 is polymorphic, add the keyword virtual
to its declaration, as follows:
public virtual
void DrawControl( )
Each derived class is free to inherit and use the base class’s DrawControl( )
method as is, or to implement its own version of DrawControl( )
. If a derived class does override the DrawControl( )
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. The Control
and ListBox
classes are back, and they’ve brought along a Button
class, which also derives from Control
.
Example 11-2. Virtual methods allow derived classes to implement their own version of the method
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Example_11_2_ _ _ _Polymorphism { public class Control { // these members are protected and thus visible // to derived class methods. protected int top; protected int left; // constructor takes two integers to // fix location on the console public Control (int top, int left) { this.top = top; this.left = left; } // simulates drawing the control publicvirtual
void DrawControl( ) { Console.WriteLine("Control: drawing Control at {0}, {1}", top, left); } } // ListBox derives from Control public class ListBox : Control { private string listBoxContents; // new member variable // 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 DrawControl( ) { base.DrawControl( ); // invoke the base method Console.WriteLine("Writing string to the ListBox: {0}", listBoxContents); } } // end ListBox // Button also derives from Control public class Button : Control { // constructor has no body because it simply calls // the base class constructor public Button( int top, int left) : base(top, left) { } // an overridden version (note keyword) because in the // derived method we change the behavior publicoverride
void DrawControl( ) { Console.WriteLine("Drawing a button at {0}, {1} ", top, left); } } // end Button public class Tester { static void Main( ) { Control myControl = new Control(1, 2); ListBox myListBox = new ListBox(3, 4, "Standalone listbox"); Button myButton = new Button(5, 6); myControl.DrawControl( ); myListBox.DrawControl( ); myButton.DrawControl( ); Control[] controlArray = new Control[3]; controlArray[0] = new Control(1, 2); controlArray[1] = new ListBox(3, 4, "Listbox in array"); controlArray[2] = new Button(5, 6); for (int i = 0; i < controlArray.Length; i++) { controlArray[i].DrawControl( ); } // end for } // end Main } // end Tester }
Control: drawing Control at 1, 2 Control: drawing Control at 3, 4 Writing string to the ListBox: Standalone listbox Drawing a button at 5, 6 Control: drawing Control at 1, 2 Control: drawing Control at 3, 4 Writing string to the ListBox: Listbox in array Drawing a button at 5, 6
In Example 11-2, ListBox
derives from Control
and implements its own version of DrawControl( )
, using the override
keyword:
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 DrawControl( )
works. Similarly, you override DrawControl( )
in another class that derives from Control
: the Button
class.
The only reason this override works is because in the base class (Control
), the DrawControl( )
method is marked as virtual
:
public virtual void DrawControl( ) { Console.WriteLine("Control: drawing Control at {0}, {1}", top, left); }
If DrawControl( )
weren’t marked as virtual
, the derived classes wouldn’t be able to override it.
The really interesting part of this example, from a polymorphic point of view, happens in the body of the example. You create three objects: a Control
, a ListBox
, and a Button
. Then you call DrawControl( )
on each:
Control myControl = new Control(1, 2); ListBox myListBox = new ListBox(3, 4, "Standalone listbox"); Button myButton = new Button(5, 6); myControl.DrawControl( ); myListBox.DrawControl( ); myButton.DrawControl( );
This works much as you might expect. The correct DrawControl( )
method is called for each. So far, nothing polymorphic has been done, because each of the three classes has its own version of DrawControl( )
, which is what you’re calling here. The real magic starts when you create an array of Control
objects. As you learned in Chapter 10, an array can contain only objects of the same type. On the face of it, then, you wouldn’t expect that you could store a Control
, a ListBox
, and a Button
all in the same array.
But because a ListBox
is a Control
, you are free to place a ListBox
into an array of Control
s. Similarly, you can add a Button
to a collection of Control
s, because a Button
is a Control
.
Control[] controlArray = new Control[3]; controlArray[0] = new Control(1, 2); controlArray[1] = new ListBox(3, 4, "Listbox in array"); controlArray[2] = new Button(5, 6);
The first line of the preceding code declares an array named controlArray
that will hold three Control
objects. The next three lines add new Control
objects to the array. The first adds an object of type Control
. The second adds an object of type ListBox
(which is a Control
because ListBox
derives from Control
), and the third adds an object of type Button
, which is also a type of Control
.
What happens when you call DrawControl( )
on each of these objects?
for (int i = 0; i < 3; i++) { controlArray[i].DrawControl( ); }
This code calls DrawControl( )
on each element in the array in turn. All the compiler knows is that it has three Control
objects and that you’ve called DrawControl( )
on each. If you had not marked DrawControl( )
as virtual, Control
’s original DrawControl( )
method would be called three times.
However, because you did mark DrawControl( )
as virtual
, and because the derived classes override that method, when you call DrawControl( )
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 Control
, a ListBox
, and a Button
) and calls the right method on each. This is the essence of polymorphism—that the for
loop, and the code within it, have no idea what kinds of objects are going to be in the array, except that they all derive from Control
, and therefore have valid DrawControl( )
methods. The for
loop doesn’t need to know any more than that.
The runtime type of an object is the actual (derived) type. At compile time, you do not have to decide what kinds of objects will be added to your collection, as long as they all derive from the declared type (in this case, Control
). 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.
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.DrawControl( )
is called when the Control
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 to prevent that problem.
Here’s how: assume for a moment that Company A wrote the Control
base class you saw previously in Example 11-2. Suppose also that the ListBox
and RadioButton
classes were written by programmers from Company B, using a purchased copy of Company A’s Control
class as a base. The programmers in Company B have little or no control over the design of the Control
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 : Control { public virtual void Sort( ) {...} }
This presents no problems until Company A, the author of Control
, releases version 2 of its Control
class, and the programmers in Company A also add a Sort( )
method to their public class Control
:
public class Control { // ... public virtual void Sort( ) {...} }
In other object-oriented languages (such as C++), the new virtual Sort( )
method in Control
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 Control
, 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 'Control.Sort( )'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
Never ignore a warning. Treat it as an error, until you are satisfied that you understand it and that not only is it innocuous, but also there is nothing you can do to eliminate it. Your goal, (almost) always, is to compile warning-free code.
To remove the warning, the programmer must indicate what he intends. He can mark the ListBox
Sort( )
method as new
to indicate that it is not an override of the virtual method in Control
:
public class ListBox : Control { public new virtual void Sort( ) {...}
This action removes the warning. If, on the other hand, the programmer does want to override the method in Control
, he need only use the override
keyword to make that intention explicit:
public class ListBox : Control { 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 the code. It points a potential client to the base class to see what 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 Control
class.