Section 11.3. Polymorphism


11.3. Polymorphism

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.

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

  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.

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.

11.3.2. Versioning with new and override

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. [*] She can mark the ListBox Sort( ) method new to indicate that it is not an override of the virtual method in Window:

[*] 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.

 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.



Learning C# 2005
Learning C# 2005: Get Started with C# 2.0 and .NET Programming (2nd Edition)
ISBN: 0596102097
EAN: 2147483647
Year: 2004
Pages: 250

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net