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

Creating Polymorphic Types

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 Controls 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 Control 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, 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
        public virtual 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
        public override 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
        public override 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
}

The output looks like this:

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

Overriding Virtual Methods

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.

Using Objects Polymorphically

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 Controls. Similarly, you can add a Button to a collection of Controls, 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.

Tip

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.

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

Warning

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( ) {...}

Warning

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.111.116