Section 5.3. Polymorphism


5.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 (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 doesn't 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 "instance" of this type knows how to ring. When the phone company tells your phone to ring, it simply expects the phone to "do the right thing." Thus, the phone company treats your phone polymorphically.

5.3.1. Creating Polymorphic Types

Because a ListBox is-a Control and a Button is-a Control, we 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 instances of Control it manages so that when the form is opened, it can tell each of its Controls to draw itself. For this operation, the form doesn't want to know which elements are listboxes and which are buttons; it just wants to tick through its collection and tell each to "draw." In short, the form wants to treat all its Control objects polymorphically.

5.3.2. Creating Polymorphic Methods

To create a method that supports polymorphism, you need only mark it as virtual in its base class. For example, to indicate that the method DrawWindow( ) of class Control in Example 5-1 is polymorphic, simply add the keyword virtual to its declaration as follows:

public virtual void DrawWindow()

Now each derived class is free to implement its own version of DrawWindow( ). To do so, simply override the base class virtual method by using the keyword override in the derived class method definition, and then add the new code for that overridden method.

In the following excerpt from Example 5-1 (which appears later in this section), ListBox derives from Control 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 this method in another class, Button, also derived from Control.

In the body of Example 5-1, you'll first create three objects: a Control, a ListBox, and a Button. You'll then call DrawWindow( ) on each:

Control win = new Control(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( ) object is called for each. So far, nothing polymorphic has been done. The real magic starts when you create an array of Control objects. Because a ListBox is-a Control, you are free to place a ListBox into a Control array. You can also place a Button into an array of Control objects because a Button is also a Control:

Control[] winArray = new Control[3]; winArray[0] = new Control(1,2); winArray[1] = new ListBox(3,4,"List box in array"); winArray[2] = new Button(5,6);

What happens when you call DrawWindow( ) on each object?

for (int i = 0;i < 3; i++) {    winArray[i].DrawWindow(); }

All the compiler knows is that it has three Control objects and that you've called DrawWindow() on each. If you had not marked DrawWindow as virtual, Control's 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 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. The complete code for this example is shown in Example 5-1.

This listing uses an array, which is a collection of objects of the same type. Access the members of the array with the index operator:


// set the value of the element // at offset 5 MyArray[5] = 7;

The first element in any array is at index 0. The use of the array in this example should be fairly intuitive. Arrays are explained in detail in Chapter 9.

Example 5-1. Using virtual methods
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace VirtualMethods {    public class Control    {       // these members are protected and thus visible       // to derived class methods. We'll examine this        // later in the chapter       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 window       public virtual void DrawWindow( )       {          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       public ListBox(          int top,          int left,          string contents ):       base(top, left)  // call base constructor       {          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 );       }    }    public class Button : Control    {       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 );       }    }    public class Tester    {       static void Main( )       {          Control win = new Control( 1, 2 );          ListBox lb = new ListBox( 3, 4, "Stand alone list box" );          Button b = new Button( 5, 6 );          win.DrawWindow( );          lb.DrawWindow( );          b.DrawWindow( );          Control[] winArray = new Control[3];          winArray[0] = new Control( 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( );          }       }    } } Output: Control: drawing Control at 1, 2 Control: drawing Control at 3, 4 Writing string to the listbox: Stand alone list box Drawing a button at 5, 6 Control: drawing Control at 1, 2 Control: drawing Control at 3, 4 Writing string to the listbox: List box in array Drawing a button at 5, 6

Note that throughout this example we've marked the new overridden methods 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 it is ListBox.DrawWindow( ) that is called when the Control reference really points to a ListBox object.

C++ programmers take note: you must explicitly mark the declaration of any method that overrides a virtual method with the keyword override.


5.3.3. Calling Base Class Constructors

In Example 5-1, the new class ListBox derives from Control and has its own constructor, which takes three parameters. The ListBox constructor invokes the constructor of its parent (Control) by placing a colon (:) after the parameter list and then invoking the base class with the keyword base:

public ListBox(     int theTop,      int theLeft,      string theContents):         base(theTop, theLeft)  // call base constructor

Because classes can't inherit constructors, a derived class must implement its own constructor and can only make use of the constructor of its base class by calling it explicitly.

If the base class has an accessible default constructor, the derived constructor is not required to invoke the base constructor explicitly; instead, the default constructor is called implicitly. However, if the base class doesn't have a default constructor, every derived constructor must explicitly invoke one of the base class constructors using the base keyword.

As discussed in Chapter 4, if you don't declare a constructor of any kind, the compiler will create a default constructor for you. Whether you write it yourself or you use the one provided "by default" by the compiler, a default constructor is one that takes no parameters. Note, however, that once you do create a constructor of any kind (with or without parameters), the compiler doesn't create a default constructor for you.


5.3.4. Controlling Access

The visibility of a class and its members can be restricted through the use of access modifiers, such as public, private, protected, internal, and protected internal. (See Chapter 4 for a discussion of access modifiers.)

As you've seen, public allows a member to be accessed by the member methods of other classes, while private indicates that the member is visible only to member methods of its own class. The protected keyword extends visibility to methods of derived classes, while internal extends visibility to methods of any class in the same assembly.[1]

[1] An assembly (discussed in Chapter 1) is the unit of sharing and reuse in the CLR (a logical DLL). Typically, an assembly is created from a collection of physical files, held in a single directory that includes all the resources (bitmaps, .gif files, etc.) required for an executable, along with the IL and metadata for that program.

The internal protected keyword pair allows access to members of the same assembly (internal) or derived classes (protected). You can think of this designation as internal or protected.

Classes as well as their members can be designated with any of these accessibility levels. If a class member has an access designation that is different from that of the class, the more restricted access applies. Thus, if you define a class, myClass, as follows:

public class myClass {    // ...    protected int myValue; }

the accessibility for myValue is protected even though the class itself is public. A public class is one that is visible to any other class that wishes to interact with it. Often, classes are created that exist only to help other classes in an assembly, and these classes might be marked internal rather than public.

5.3.5. Versioning with the new and override Keywords

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 keyword override helps prevent that problem.

Here's how: assume for a moment that the Control base class of the previous example was written by Company A. Suppose also that the ListBox and RadioButton classes were written by programmers from Company B using a purchased copy of the Company A 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 it turns out that the programmers in Company A have also added 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 method for the virtual Sort() method in ListBox. The compiler would call the Sort() method in ListBox when you intend to call the Sort( ) in Control. In Java, if the Sort() in Control has a different return type, the class loader would consider the Sort() in ListBox to be an invalid override and would fail to load.

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.

To remove the warning, the programmer must indicate what he intends. He can mark the ListBox Sort( ) method 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 keyword new 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 you aren't overriding. Using new scattershot undermines this documentation. Further, the warning exists to help identify a real issue.




Programming C#(c) Building. NET Applications with C#
Programming C#: Building .NET Applications with C#
ISBN: 0596006993
EAN: 2147483647
Year: 2003
Pages: 180
Authors: Jesse Liberty

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