Virtual Methods and Overriding


A virtual method is a method that is declared as virtual in a base class and redefined in one or more derived classes. Thus, each derived class can have its own version of a virtual method. Virtual methods are interesting because of what happens when one is called through a base class reference. In this situation, C# determines which version of the method to call based upon the type of the object referred to by the reference—and this determination is made at runtime. Thus, when different types of objects are referred to, different versions of the virtual method are executed. In other words, it is the type of the object being referred to (not the type of the reference) that determines which version of the virtual method will be executed. Therefore, if a base class contains a virtual method and classes are derived from that base class, then when different types of objects are referred to through a base class reference, different versions of the virtual method are executed.

You declare a method as virtual inside a base class by preceding its declaration with the keyword virtual. When a virtual method is redefined by a derived class, the override modifier is used. Thus, the process of redefining a virtual method inside a derived class is called method overriding. When overriding a method, the names and the type signatures of the override method must be the same as the virtual method that is being overridden. Also, a virtual method cannot be specified as static or abstract (discussed later in this chapter).

Method overriding forms the basis for one of C#’s most powerful concepts: dynamic method dispatch. Dynamic method dispatch is the mechanism by which a call to an overridden method is resolved at runtime, rather than compile time. Dynamic method dispatch is important because this is how C# implements runtime polymorphism.

Here is an example that illustrates virtual methods and overriding:

 // Demonstrate a virtual method. using System; class Base {   // Create virtual method in the base class.   public virtual void who() {     Console.WriteLine("who() in Base");   } } class Derived1 : Base {   // Override who() in a derived class.   public override void who() {     Console.WriteLine("who() in Derived1");   } } class Derived2 : Base {   // Override who() again in another derived class.   public override void who() {     Console.WriteLine("who() in Derived2");   } } class OverrideDemo {   public static void Main() {     Base baseOb = new Base();     Derived1 dOb1 = new Derived1();     Derived2 dOb2 = new Derived2();     Base baseRef; // a base-class reference     baseRef = baseOb;     baseRef.who();     baseRef = dOb1;     baseRef.who();     baseRef = dOb2;     baseRef.who();   } }

The output from the program is shown here:

 who() in Base who() in Derived1 who() in Derived2

This program creates a base class called Base and two derived classes of it, called Derived1 and Derived2. Base declares a method called who( ), and the derived classes override it. Inside the Main( ) method, objects of type Base, Derived1, and Derived2 are declared. Also, a reference of type Base, called baseRef, is declared. The program then assigns a reference to each type of object to baseRef and uses that reference to call who( ). As the output shows, the version of who( ) executed is determined by the type of object being referred to at the time of the call, not by the class type of baseRef.

It is not necessary to override a virtual method. If a derived class does not provide its own version of a virtual method, then the one in the base class is used. For example:

 /* When a virtual method is not overridden,    the base class method is used. */ using System; class Base {   // Create virtual method in the base class.   public virtual void who() {     Console.WriteLine("who() in Base");   } } class Derived1 : Base {   // Override who() in a derived class.   public override void who() {     Console.WriteLine("who() in Derived1");   } } class Derived2 : Base {   // This class does not override who(). } class NoOverrideDemo {   public static void Main() {     Base baseOb = new Base();     Derived1 dOb1 = new Derived1();     Derived2 dOb2 = new Derived2();     Base baseRef; // a base-class reference     baseRef = baseOb;     baseRef.who();     baseRef = dOb1;     baseRef.who();     baseRef = dOb2;     baseRef.who(); // calls Base's who()   } }

The output from this program is shown here:

 who() in Base who() in Derived1 who() in Base

Here, Derived2 does not override who( ). Thus, when who( ) is called on a Derived2 object, the who( ) in Base is executed.

In the case of a multilevel hierarchy, if a derived class does not override a virtual method, then, while moving up the hierarchy, the first override of the method that is encountered is the one executed. For example:

 /*  In a multilevel hierarchy, the     first override of a virtual method     that is found while moving up the     hierarchy is the one executed. */ using System; class Base {   // Create virtual method in the base class.   public virtual void who() {     Console.WriteLine("who() in Base");   } } class Derived1 : Base {   // Override who() in a derived class.   public override void who() {     Console.WriteLine("who() in Derived1");   } } class Derived2 : Derived1 {   // This class also does not override who(). } class Derived3 : Derived2 {   // This class does not override who(). } class NoOverrideDemo2 {   public static void Main() {     Derived3 dOb = new Derived3();     Base baseRef; // a base-class reference     baseRef = dOb;     baseRef.who(); // calls Derived1's who()   } }

The output is shown here:

 who() in Derived1

Here, Derived3 inherits Derived2, which inherits Derived1, which inherits Base. As the output verifies, since who( ) is not overridden by either Derived3 or Derived2, it is the override of who( ) in Derived1 that is executed, because it is the first version of who( ) that is found.

One other point: Properties can also be modified by the virtual keyword and overridden using override.

Why Overridden Methods?

Overridden methods allow C# to support runtime polymorphism. Polymorphism is essential to object-oriented programming for one reason: it allows a general class to specify methods that will be common to all of its derivatives, while allowing derived classes to define the specific implementation of some or all of those methods. Overridden methods are another way that C# implements the “one interface, multiple methods” aspect of polymorphism.

Part of the key to successfully applying polymorphism is understanding that the base classes and derived classes form a hierarchy that moves from lesser to greater specialization. Used correctly, the base class provides all elements that a derived class can use directly. It also defines those methods that the derived class must implement on its own. This allows the derived class the flexibility to define its own methods, yet still enforces a consistent interface. Thus, by combining inheritance with overridden methods, a base class can define the general form of the methods that will be used by all of its derived classes.

Applying Virtual Methods

To better understand the power of virtual methods, we will apply them to the TwoDShape class. In the preceding examples, each class derived from TwoDShape defines a method called area( ). This suggests that it might be better to make area( ) a virtual method of the TwoDShape class, allowing each derived class to override it, defining how the area is calculated for the type of shape that the class encapsulates. The following program does this. For convenience, it also adds a name property to TwoDShape. (This makes it easier to demonstrate the classes.)

 // Use virtual methods and polymorphism. using System; class TwoDShape {   double pri_width;  // private   double pri_height; // private   string pri_name;   // private   // A default constructor.   public TwoDShape() {     width = height = 0.0;     name = "null";   }   // Parameterized constructor.   public TwoDShape(double w, double h, string n) {     width = w;     height = h;     name = n;   }   // Construct object with equal width and height.   public TwoDShape(double x, string n) {     width = height = x;     name = n;   }   // Construct an object from an object.   public TwoDShape(TwoDShape ob) {     width = ob.width;     height = ob.height;     name = ob.name;   }   // Properties for width, height, and name   public double width {     get { return pri_width; }     set { pri_width = value; }   }   public double height {     get { return pri_height; }     set { pri_height = value; }   }   public string name {     get { return pri_name; }     set { pri_name = value; }   }   public void showDim() {     Console.WriteLine("Width and height are " +                        width + " and " + height);   }   public virtual double area() {     Console.WriteLine("area() must be overridden");     return 0.0;   } } // A derived class of TwoDShape for triangles. class Triangle : TwoDShape {   string style; // private   // A default constructor.   public Triangle() {     style = "null";   }   // Constructor for Triangle.   public Triangle(string s, double w, double h) :     base(w, h, "triangle") {       style = s;   }   // Construct an isosceles triangle.   public Triangle(double x) : base(x, "triangle") {     style = "isosceles";   }   // Construct an object from an object.   public Triangle(Triangle ob) : base(ob) {     style = ob.style;   }   // Override area() for Triangle.   public override double area() {     return width * height / 2;   }   // Display a triangle's style.   public void showStyle() {     Console.WriteLine("Triangle is " + style);   } } // A derived class of TwoDShape for rectangles. class Rectangle : TwoDShape {   // Constructor for Rectangle.   public Rectangle(double w, double h) :     base(w, h, "rectangle"){ }   // Construct a square.   public Rectangle(double x) :     base(x, "rectangle") { }   // Construct an object from an object.   public Rectangle(Rectangle ob) : base(ob) { }   // Return true if the rectangle is square.   public bool isSquare() {     if(width == height) return true;     return false;   }   // Override area() for Rectangle.   public override double area() {     return width * height;   } } class DynShapes {   public static void Main() {     TwoDShape[] shapes = new TwoDShape[5];     shapes[0] = new Triangle("right", 8.0, 12.0);     shapes[1] = new Rectangle(10);     shapes[2] = new Rectangle(10, 4);     shapes[3] = new Triangle(7.0);     shapes[4] = new TwoDShape(10, 20, "generic");     for(int i=0; i < shapes.Length; i++) {       Console.WriteLine("object is " + shapes[i].name);       Console.WriteLine("Area is " + shapes[i].area());       Console.WriteLine();     }   } }

The output from the program is shown here:

 object is triangle Area is 48 object is rectangle Area is 100 object is rectangle Area is 40 object is triangle Area is 24.5 object is generic area() must be overridden Area is 0

Let’s examine this program closely. First, as explained, area( ) is declared as virtual in the TwoDShape class and is overridden by Triangle and Rectangle. Inside TwoDShape, area( ) is given a placeholder implementation that simply informs the user that this method must be overridden by a derived class. Each override of area( ) supplies an implementation that is suitable for the type of object encapsulated by the derived class. Thus, if you were to implement an ellipse class, for example, then area( ) would need to compute the area( ) of an ellipse.

There is one other important feature in the preceding program. Notice in Main( ) that shapes is declared as an array of TwoDShape objects. However, the elements of this array are assigned Triangle, Rectangle, and TwoDShape references. This is valid because a base class reference can refer to a derived class object. The program then cycles through the array, displaying information about each object. Although quite simple, this illustrates the power of both inheritance and method overriding. If an object is derived from TwoDShape, then its area can be obtained by calling area( ). The interface to this operation is the same no matter what type of shape is being used.




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

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