CHAPTER 11
Inheritance

Inheritance is one of the three foundational principles of object-oriented programming because it allows the creation of hierarchical classifications. Using inheritance, you can create a general class that defines traits common to a set of related items. This class can then be inherited by other, more specific classes, each adding those things that are unique to it.

In the language of C#, a class that is inherited is called a base class. The class that does the inheriting is called a derived class. Therefore, a derived class is a specialized version of a base class. It inherits all of the variables, methods, properties, and indexers defined by the base class and adds its own unique elements.

Inheritance Basics

C# supports inheritance by allowing one class to incorporate another class into its declaration. This is done by specifying a base class when a derived class is declared. Let’s begin with an example. The following class called TwoDShape stores the width and height of a two-dimensional object, such as a square, rectangle, triangle, and so on.

// A class for two-dimensional objects.
class TwoDShape {
  public double Width;
  public double Height;

  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                       Width + " and " + Height);
  }
}

TwoDShape can be used as a base class (that is, as a starting point) for classes that describe specific types of two-dimensional objects. For example, the following program uses TwoDShape to derive a class called Triangle. Pay close attention to the way that Triangle is declared.

// A simple class hierarchy.

using System;

// A class for two-dimensional objects.
class TwoDShape {
  public double Width;
  public double Height;

  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                       Width + " and " + Height);
  }
}
// Triangle is derived from TwoDShape.
class Triangle : TwoDShape {
  public string Style; // style of triangle

  // Return area of triangle.
  public double Area() {
    return Width * Height / 2;
  }

  // Display a triangle's style.
  public void ShowStyle() {
    Console.WriteLine("Triangle is " + Style);
  }
}

class Shapes {
  static void Main() {
    Triangle t1 = new Triangle();
    Triangle t2 = new Triangle();

    t1.Width = 4.0;
    t1.Height = 4.0;
    t1.Style = "isosceles";

    t2.Width = 8.0;
    t2.Height = 12.0;
    t2.Style = "right";

    Console.WriteLine("Info for t1: ");
    t1.ShowStyle();
    t1.ShowDim();
    Console.WriteLine("Area is " + t1.Area());

    Console.WriteLine();

    Console.WriteLine("Info for t2: ");
    t2.ShowStyle();
    t2.ShowDim();
    Console.WriteLine("Area is " + t2.Area());
 }
}

The output from this program is shown here:

Info for t1:
Triangle is isosceles
Width and height are 4 and 4
Area is 8

Info for t2:
Triangle is right
Width and height are 8 and 12
Area is 48

The Triangle class creates a specific type of TwoDShape, in this case, a triangle. The Triangle class includes all of TwoDShape and adds the field Style, the method Area( ), and the method ShowStyle( ). A description of the type of triangle is stored in Style; Area( ) computes and returns the area of the triangle; and ShowStyle( ) displays the triangle style.

Notice the syntax that Triangle uses to inherit TwoDShape:

class Triangle : TwoDShape {

This syntax can be generalized. Whenever one class inherits another, the base class name follows the name of the derived class, separated by a colon. In C#, the syntax for inheriting a class is remarkably simple and easy to use.

Because Triangle includes all of the members of its base class, TwoDShape, it can access Width and Height inside Area( ). Also, inside Main( ), objects t1 and t2 can refer to Width and Height directly, as if they were part of Triangle. Figure 11-1 depicts conceptually how TwoDShape is incorporated into Triangle.

Even though TwoDShape is a base for Triangle, it is also a completely independent, stand-alone class. Being a base class for a derived class does not mean that the base class cannot be used by itself. For example, the following is perfectly valid:

TwoDShape shape = new TwoDShape();

shape.Width = 10;
shape.Height = 20;

shape.ShowDim();

Of course, an object of TwoDShape has no knowledge of or access to any classes derived from TwoDShape.

Image

FIGURE 11-1 A conceptual depiction of the Triangle class

The general form of a class declaration that inherits a base class is shown here:

class derived-class-name: base-class-name {

// body of class

}

You can specify only one base class for any derived class that you create. C# does not support the inheritance of multiple base classes into a single derived class. (This differs from C++, in which you can inherit multiple base classes. Be aware of this when converting C++ code to C#.) You can, however, create a hierarchy of inheritance in which a derived class becomes a base class of another derived class. (Of course, no class can be a base class of itself, either directly or indirectly.) In all cases, a derived class inherits all of the members of its base class. This includes instance variables, methods, properties, and indexers.

A major advantage of inheritance is that once you have created a base class that defines the attributes common to a set of objects, it can be used to create any number of more specific derived classes. Each derived class can precisely tailor its own classification. For example, here is another class derived from TwoDShape that encapsulates rectangles:

// A derived class of TwoDShape for rectangles.
class Rectangle : TwoDShape {
  // Return true if the rectangle is square.
  public bool IsSquare() {
    if(Width == Height) return true;
    return false;
  }

  // Return area of the rectangle.
  public double Area() {
    return Width * Height;
  }
}

The Rectangle class includes TwoDShape and adds the methods IsSquare( ), which determines if the rectangle is square, and Area( ), which computes the area of a rectangle.

Member Access and Inheritance

As explained in Chapter 8, members of a class are often declared private to prevent their unauthorized use or tampering. Inheriting a class does not overrule the private access restriction. Thus, even though a derived class includes all of the members of its base class, it cannot access those members of the base class that are private. For example, if, as shown here, Width and Height are made private in TwoDShape, then Triangle will not be able to access them:

// Access to private members is not inherited.

// This example will not compile.
using System;

// A class for two-dimensional objects.
class TwoDShape {
  double Width; // now private
  double Height; // now private

  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                       Width + " and " + Height);
  }
}

// Triangle is derived from TwoDShape.
class Triangle : TwoDShape {
  public string Style; // style of triangle

  // Return area of triangle.
  public double Area() {
    return Width * Height / 2; // Error, can't access private member
  }

  // Display a triangle's style.
  public void ShowStyle() {
    Console.WriteLine("Triangle is " + Style);
  }
}

The Triangle class will not compile because the use of Width and Height inside the Area( ) method is illegal. Since Width and Height are now private, they are accessible only to other members of their own class. Derived classes have no access to them.

REMEMBER A private class member will remain private to its class. It is not accessible to any code outside its class, including derived classes.

At first, you might think that it is a serious restriction that derived classes do not have access to the private members of base classes because it would prevent the use of private members in many situations. However, this is not true; C# provides various solutions. One is to use protected members, which is described in the next section. A second is to use public properties to provide access to private data.

As explained in the previous chapter, a property allows you to manage access to an instance variable. For example, you can enforce constraints on its values, or you can make the variable read-only. By making a property public, but declaring its underlying variable private, a derived class can still use the property, but it cannot directly access the underlying private variable.

Here is a rewrite of the TwoDShape class that makes Width and Height into properties. In the process, it ensures that the values of Width and Height will be positive. This would allow you, for example, to specify the Width and Height using the coordinates of the shape in any quadrant of the Cartesian plane without having to first obtain their absolute values.

// Use public properties to set and get private members.

using System;

// A class for two-dimensional objects.
class TwoDShape {
  double pri_width; // now private
  double pri_height; // now private

  // Properties for width and height.
  public double Width {
    get { return pri_width; }
    set { pri_width = value < 0 ? -value : value; }
  }

  public double Height {
    get { return pri_height; }
    set { pri_height = value < 0 ? -value : value; }
  }

  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                       Width + " and " + Height);
  }
}

// A derived class of TwoDShape for triangles.
class Triangle : TwoDShape {
  public string Style; // style of triangle

  // Return area of triangle.
  public double Area() {
    return Width * Height / 2;
  }

  // Display a triangle's style.
  public void ShowStyle() {
    Console.WriteLine("Triangle is " + Style);
  }
}
class Shapes2 {
  static void Main() {
    Triangle t1 = new Triangle();
    Triangle t2 = new Triangle();

    t1.Width = 4.0;
    t1.Height = 4.0;
    t1.Style = "isosceles";

    t2.Width = 8.0;
    t2.Height = 12.0;
    t2.Style = "right";

    Console.WriteLine("Info for t1: ");
    t1.ShowStyle();
    t1.ShowDim();
    Console.WriteLine("Area is " + t1.Area());
    Console.WriteLine();

    Console.WriteLine("Info for t2: ");
    t2.ShowStyle();
    t2.ShowDim();
    Console.WriteLine("Area is " + t2.Area());
  }
}

In this version, the properties Width and Height provide access to the private members, pri_width and pri_height, which actually store the values. Therefore, even though pri_width and pri_height are private to TwoDShape, their values can still be set and obtained through their corresponding public properties.

When referring to base and derived classes, sometimes the terms superclass and subclass are used. These terms come from Java programming. What Java calls a superclass, C# calls a base class. What Java calls a subclass, C# calls a derived class. You will commonly hear both sets of terms applied to a class of either language, but this book will continue to use the standard C# terms. C++ also uses the base-class/derived-class terminology.

Using Protected Access

As just explained, a private member of a base class is not accessible to a derived class. This would seem to imply that if you wanted a derived class to have access to some member in the base class, it would need to be public. Of course, making the member public also makes it available to all other code, which may not be desirable. Fortunately, this implication is untrue because C# allows you to create a protected member. A protected member is public within a class hierarchy, but private outside that hierarchy.

A protected member is created by using the protected access modifier. When a member of a class is declared as protected, that member is, with one important exception, private. The exception occurs when a protected member is inherited. In this case, a protected member of the base class becomes a protected member of the derived class and is, therefore, accessible to the derived class. Therefore, by using protected, you can create class members that are private to their class but that can still be inherited and accessed by a derived class.

Here is a simple example that uses protected:

// Demonstrate protected.

using System;

class B {
  protected int i, j; // private to B, but accessible by D

  public void Set(int a, int b) {
    i = a;
    j = b;
  }

  public void Show() {
    Console.WriteLine(i + " " + j);
 }
}
class D : B {
  int k; // private

  // D can access B's i and j
  public void Setk() {
     k = i * j;
  }

  public void Showk() {
    Console.WriteLine(k);
  }
 }

class ProtectedDemo {
  static void Main() {
    D ob = new D();

    ob.Set(2, 3); // OK, known to D
    ob.Show();   // OK, known to D

    ob.Setk(); // OK, part of D
    ob.Showk(); // OK, part of D
  }
}

In this example, because B is inherited by D and because i and j are declared as protected in B, the Setk( ) method can access them. If i and j had been declared as private by B, then D would not have access to them, and the program would not compile.

Like public and private, protected status stays with a member no matter how many layers of inheritance are involved. Therefore, when a derived class is used as a base class for another derived class, any protected member of the initial base class that is inherited by the first derived class is also inherited as protected by a second derived class.

Although protected access is quite useful, it doesn’t apply in all situations. For example, in the case of TwoDShape shown in the preceding section, we specifically want the Width and Height values to be publicly accessible. It’s just that we want to manage the values they are assigned. Therefore, declaring them protected is not an option. In this case, the use of properties supplies the proper solution by controlling, rather than preventing, access. Remember, use protected when you want to create a member that is accessible throughout a class hierarchy, but otherwise private. To manage access to a value, use a property.

Constructors and Inheritance

In a hierarchy, it is possible for both base classes and derived classes to have their own constructors. This raises an important question: What constructor is responsible for building an object of the derived class? The one in the base class, the one in the derived class, or both? Here is the answer: The constructor for the base class constructs the base class portion of the object, and the constructor for the derived class constructs the derived class part. This makes sense because the base class has no knowledge of or access to any element in a derived class. Thus, their construction must be separate. The preceding examples have relied upon the default constructors created automatically by C#, so this was not an issue. However, in practice, most classes will define constructors. Here you will see how to handle this situation.

When only the derived class defines a constructor, the process is straightforward: Simply construct the derived class object. The base class portion of the object is constructed automatically using its default constructor. For example, here is a reworked version of Triangle that defines a constructor. It also makes Style private since it is now set by the constructor.

// Add a constructor to Triangle.

using System;

// A class for two-dimensional objects.
class TwoDShape {
  double pri_width;
  double pri_height;

  // Properties for Width and Height.
  public double Width {
     get { return pri_width; }
     set { pri_width = value < 0 ? -value : value; }
  }

  public double Height {
     get { return pri_height; }
     set { pri_height = value < 0 ? -value : value; }
  }

  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                       Width + " and " + Height);
  }
}

// A derived class of TwoDShape for triangles.
class Triangle : TwoDShape {
  string Style;

  // Constructor.
  public Triangle(string s, double w, double h) {
    Width = w; // init the base class
    Height = h; // init the base class

    Style = s; // init the derived class
  }

  // Return area of triangle.
  public double Area() {
    return Width * Height / 2;
  }

  // Display a triangle's style.
  public void ShowStyle() {
    Console.WriteLine("Triangle is " + Style);
  }
}
class Shapes3 {
  static void Main() {
    Triangle t1 = new Triangle("isosceles", 4.0, 4.0);
    Triangle t2 = new Triangle("right", 8.0, 12.0);

    Console.WriteLine("Info for t1: ");
    t1.ShowStyle();
    t1.ShowDim();
    Console.WriteLine("Area is " + t1.Area());

    Console.WriteLine();

    Console.WriteLine("Info for t2: ");
    t2.ShowStyle();
    t2.ShowDim();
    Console.WriteLine("Area is " + t2.Area());
  }
}

Here, Triangle’s constructor initializes the members of TwoDShape that it inherits along with its own Style field.

When both the base class and the derived class define constructors, the process is a bit more complicated because both the base class and derived class constructors must be executed. In this case, you must use another of C#’s keywords, base, which has two uses. The first use is to call a base class constructor. The second is to access a member of the base class that has been hidden by a member of a derived class. Here, we will look at its first use.

Calling Base Class Constructors

A derived class can call a constructor defined in its base class by using an expanded form of the derived class’ constructor declaration and the base keyword. The general form of this expanded declaration is shown here:

derived-constructor(parameter-list): base(arg-list) {

// body of constructor

}

Here, arg-list specifies any arguments needed by the constructor in the base class. Notice the placement of the colon.

To see how base is used, consider the version of TwoDShape in the following program. It defines a constructor that initializes the Width and Height properties. This constructor is then called by the Triangle constructor.

// Add constructor to TwoDShape.

using System;

// A class for two-dimensional objects.
class TwoDShape {
  double pri_width;
  double pri_height;
  // Constructor for TwoDShape.
  public TwoDShape(double w, double h) {
    Width = w;
    Height = h;
 }

 // Properties for Width and Height.
 public double Width {
    get { return pri_width; }
    set { pri_width = value < 0 ? -value : value; }
 }

 public double Height {
    get { return pri_height; }
    set { pri_height = value < 0 ? -value : value; }
 }

 public void ShowDim() {
   Console.WriteLine("Width and height are " +
                      Width + " and " + Height);
  }
}

  // A derived class of TwoDShape for triangles.
class Triangle : TwoDShape {
  string Style;

  // Call the base class constructor.
  public Triangle(string s, double w, double h) : base(w, h) {
    Style = s;
  }

  // Return area of triangle.
  public double Area() {
    return Width * Height / 2;
  }

  // Display a triangle's style.
  public void ShowStyle() {
    Console.WriteLine("Triangle is " + Style);
  }
}
class Shapes4 {
  static void Main() {
    Triangle t1 = new Triangle("isosceles", 4.0, 4.0);
    Triangle t2 = new Triangle("right", 8.0, 12.0);

    Console.WriteLine("Info for t1: ");
    t1.ShowStyle();
    t1.ShowDim();
    Console.WriteLine("Area is " + t1.Area());
    Console.WriteLine();

    Console.WriteLine("Info for t2: ");
    t2.ShowStyle();
    t2.ShowDim();
    Console.WriteLine("Area is " + t2.Area());
  }
}

Notice that the Triangle constructor is now declared as shown here.

public Triangle(string s, double w, double h) : base(w, h) {

In this version, Triangle( ) calls base with the parameters w and h. This causes the TwoDShape( ) constructor to be called, which initializes Width and Height using these values. Triangle no longer initializes these values itself. It need only initialize the value unique to it: Style. This leaves TwoDShape free to construct its subobject in any manner that it chooses. Furthermore, TwoDShape can add functionality about which existing derived classes have no knowledge, thus preventing existing code from breaking.

Any form of constructor defined by the base class can be called by base. The constructor executed will be the one that matches the arguments. For example, here are expanded versions of both TwoDShape and Triangle that include default constructors and constructors that take one argument.

// Add more constructors to TwoDShape.

using System;

class TwoDShape {
  double pri_width;
  double pri_height;

  // Default constructor.
  public TwoDShape() {
    Width = Height = 0.0;
  }

  // Constructor for TwoDShape.
  public TwoDShape(double w, double h) {
    Width = w;
    Height = h;
  }

  // Construct object with equal width and height.
  public TwoDShape(double x) {
    Width = Height = x;
  }

  // Properties for Width and Height.
  public double Width {
     get { return pri_width; }
     set { pri_width = value < 0 ? -value : value; }
  }
  public double Height {
    get { return pri_height; }
    set { pri_height = value < 0 ? -value : value; }
  }

  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                       Width + " and " + Height);
  }
}

// A derived class of TwoDShape for triangles.
class Triangle : TwoDShape {
  string Style;

  /* A default constructor. This automatically invokes
     the default constructor of TwoDShape. */
  public Triangle() {
    Style = "null";
  }

  // Constructor that takes three arguments.
  public Triangle(string s, double w, double h) : base(w, h) {
    Style = s;
  }

  // Construct an isosceles triangle.
  public Triangle(double x) : base(x) {
    Style = "isosceles";
  }

  // Return area of triangle.
  public double Area() {
    return Width * Height / 2;
  }

  // Display a triangle's style.
  public void ShowStyle() {
    Console.WriteLine("Triangle is " + Style);
  }
}

class Shapes5 {
  static void Main() {
    Triangle t1 = new Triangle();
    Triangle t2 = new Triangle("right", 8.0, 12.0);
    Triangle t3 = new Triangle(4.0);

    t1 = t2;

    Console.WriteLine("Info for t1: ");
    t1.ShowStyle();
    t1.ShowDim();
    Console.WriteLine("Area is " + t1.Area());
    Console.WriteLine();

    Console.WriteLine("Info for t2: ");
    t2.ShowStyle();
    t2.ShowDim();
    Console.WriteLine("Area is " + t2.Area());

    Console.WriteLine();

    Console.WriteLine("Info for t3: ");
    t3.ShowStyle();
    t3.ShowDim();
    Console.WriteLine("Area is " + t3.Area());

    Console.WriteLine();
 }
}

Here is the output from this version:

Info for t1:
Triangle is right
Width and height are 8 and 12
Area is 48

Info for t2:
Triangle is right
Width and height are 8 and 12
Area is 48

Info for t3:
Triangle is isosceles
Width and height are 4 and 4
Area is 8

Let’s review the key concepts behind base. When a derived class specifies a base clause, it is calling the constructor of its immediate base class. Thus, base always refers to the base class immediately above the calling class. This is true even in a multileveled hierarchy. You pass arguments to the base constructor by specifying them as arguments to base. If no base clause is present, then the base class’ default constructor is called automatically.

Inheritance and Name Hiding

It is possible for a derived class to define a member that has the same name as a member in its base class. When this happens, the member in the base class is hidden within the derived class. While this is not technically an error in C#, the compiler will issue a warning message. This warning alerts you to the fact that a name is being hidden. If your intent is to hide a base class member, then to prevent this warning, the derived class member must be preceded by the new keyword. Understand that this use of new is separate and distinct from its use when creating an object instance.

Here is an example of name hiding:

// An example of inheritance-related name hiding.

using System;

class A {
  public int i = 0;
}

// Create a derived class.
class B : A {
  new int i; // this i hides the i in A

  public B(int b) {
    i = b; // i in B
  }

  public void Show() {
    Console.WriteLine("i in derived class: " + i);
  }
}

class NameHiding {
  static void Main() {
    B ob = new B(2);

    ob.Show();
  }
}

First, notice the use of new in this line.

new int i; // this i hides the i in A

In essence, it tells the compiler that you know a new variable called i is being created that hides the i in the base class A. If you leave new out, a warning is generated.

The output produced by this program is shown here:

i in derived class: 2

Since B defines its own instance variable called i, it hides the i in A. Therefore, when Show( ) is invoked on an object of type B, the value of i as defined by B is displayed—not the one defined in A.

Using base to Access a Hidden Name

There is a second form of base that acts somewhat like this, except that it always refers to the base class of the derived class in which it is used. This usage has the following general form:

base.member

Here, member can be either a method or an instance variable. This form of base is most applicable to situations in which member names of a derived class hide members by the same name in the base class. Consider this version of the class hierarchy from the preceding example:

// Using base to overcome name hiding.

using System;

class A {
  public int i = 0;
}

// Create a derived class.
class B : A {
  new int i; // this i hides the i in A

  public B(int a, int b) {
    base.i = a; // this uncovers the i in A
    i = b; // i in B
  }

  public void Show() {
    // This displays the i in A.
    Console.WriteLine("i in base class: " + base.i);

   // This displays the i in B.
   Console.WriteLine("i in derived class: " + i);
  }
}

class UncoverName {
  static void Main() {
    B ob = new B(1, 2);

    ob.Show();
  }
}

This program displays the following:

i in base class: 1
i in derived class: 2

Although the instance variable i in B hides the i in A, base allows access to the i defined in the base class.

Hidden methods can also be called through the use of base. For example, in the following code, class B inherits class A, and both A and B declare a method called Show( ). Inside, B’s Show( ), the version of Show( ) defined by A is called through the use of base.

// Call a hidden method.

using System;

class A {
  public int i = 0;

  // Show() in A
  public void Show() {
    Console.WriteLine("i in base class: " + i);
  }
}

// Create a derived class.
class B : A {
  new int i; // this i hides the i in A

  public B(int a, int b) {
    base.i = a; // this uncovers the i in A
    i = b; // i in B
  }

  // This hides Show() in A. Notice the use of new.
  new public void Show() {
    base.Show(); // this calls Show() in A

    // this displays the i in B
    Console.WriteLine("i in derived class: " + i);
  }
}
class UncoverName {
  static void Main() {
    B ob = new B(1, 2);

    ob.Show();
 }
}

The output from the program is shown here:

i in base class: 1
i in derived class: 2

As you can see, base.Show( ) calls the base class version of Show( ).

One other point: Notice that new is used in this program to tell the compiler that you know a new method called Show( ) is being declared that hides the Show( ) in A.

Creating a Multilevel Hierarchy

Up to this point, we have been using simple class hierarchies consisting of only a base class and a derived class. However, you can build hierarchies that contain as many layers of inheritance as you like. As mentioned, it is perfectly acceptable to use a derived class as a base class of another. For example, given three classes called A, B, and C, C can be derived from B, which can be derived from A. When this type of situation occurs, each derived class inherits all of the traits found in all of its base classes. In this case, C inherits all aspects of B and A.

To see how a multilevel hierarchy can be useful, consider the following program. In it, the derived class Triangle is used as a base class to create the derived class called ColorTriangle. ColorTriangle inherits all of the traits of Triangle and TwoDShape and adds a field called color, which holds the color of the triangle.

// A multilevel hierarchy.

using System;

class TwoDShape {
  double pri_width;
  double pri_height;

  // Default constructor.
  public TwoDShape() {
    Width = Height = 0.0;
  }

  // Constructor for TwoDShape.
  public TwoDShape(double w, double h) {
    Width = w;
    Height = h;
  }

  // Construct object with equal width and height.
  public TwoDShape(double x) {
    Width = Height = x;
  }

  // Properties for Width and Height.
  public double Width {
     get { return pri_width; }
     set { pri_width = value < 0 ? -value : value; }
  }

  public double Height {
     get { return pri_height; }
     set { pri_height = value < 0 ? -value : value; }
  }

  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                      Width + " and " + Height);
  }
}

// A derived class of TwoDShape for triangles.
class Triangle : TwoDShape {
  string Style; // private

  /* A default constructor. This invokes the default
     constructor of TwoDShape. */
  public Triangle() {
    Style = "null";
 }
 // Constructor.
 public Triangle(string s, double w, double h) : base(w, h) {
   Style = s;
 }

 // Construct an isosceles triangle.
 public Triangle(double x) : base(x) {
   Style = "isosceles";
 }

 // Return area of triangle.
 public double Area() {
   return Width * Height / 2;
 }

 // Display a triangle's style.
 public void ShowStyle() {
   Console.WriteLine("Triangle is " + Style);
 }
}

// Extend Triangle.
class ColorTriangle : Triangle {
  string color;

  public ColorTriangle(string c, string s,
             double w, double h) : base(s, w, h) {
    color = c;
  }

  // Display the color.
  public void ShowColor() {
    Console.WriteLine("Color is " + color);
  }
}
class Shapes6 {
  static void Main() {
    ColorTriangle t1 =
         new ColorTriangle("Blue", "right", 8.0, 12.0);
    ColorTriangle t2 =
         new ColorTriangle("Red", "isosceles", 2.0, 2.0);

    Console.WriteLine("Info for t1: ");
    t1.ShowStyle();
    t1.ShowDim();
    t1.ShowColor();
    Console.WriteLine("Area is " + t1.Area());

    Console.WriteLine();

    Console.WriteLine("Info for t2: ");
    t2.ShowStyle();
    t2.ShowDim();
    t2.ShowColor();
    Console.WriteLine("Area is " + t2.Area());
  }
}

The output of this program is shown here:

Info for t1:
Triangle is right
Width and height are 8 and 12
Color is Blue
Area is 48

Info for t2:
Triangle is isosceles
Width and height are 2 and 2
Color is Red
Area is 2

Because of inheritance, ColorTriangle can make use of the previously defined classes of Triangle and TwoDShape, adding only the extra information it needs for its own, specific application. This is part of the value of inheritance; it allows the reuse of code.

This example illustrates one other important point: base always refers to the constructor in the closest base class. The base in ColorTriangle calls the constructor in Triangle. The base in Triangle calls the constructor in TwoDShape. In a class hierarchy, if a base class constructor requires parameters, then all derived classes must pass those parameters “up the line.” This is true whether or not a derived class needs parameters of its own.

When Are Constructors Called?

In the foregoing discussion of inheritance and class hierarchies, an important question may have occurred to you: When a derived class object is created, whose constructor is executed first? The one in the derived class or the one defined by the base class? For example, given a derived class called B and a base class called A, is A’s constructor called before B’s, or vice versa? The answer is that in a class hierarchy, constructors are called in order of derivation, from base class to derived class. Furthermore, this order is the same whether or not base is used. If base is not used, then the default (parameterless) constructor of each base class will be executed. The following program illustrates the order of constructor execution:

// Demonstrate when constructors are called.

using System;

// Create a base class.
class A {
  public A() {
    Console.WriteLine("Constructing A.");
  }
}

// Create a class derived from A.
class B : A {
  public B() {
    Console.WriteLine("Constructing B.");
  }
}

// Create a class derived from B.
class C : B {
  public C() {
    Console.WriteLine("Constructing C.");
  }
}

class OrderOfConstruction {
  static void Main() {
    C c = new C();
  }
}

The output from this program is shown here:

Constructing A.
Constructing B.
Constructing C.

As you can see, the constructors are called in order of derivation.

If you think about it, it makes sense that constructors are executed in order of derivation. Because a base class has no knowledge of any derived class, any initialization it needs to perform is separate from and possibly prerequisite to any initialization performed by the derived class. Therefore, it must be executed first.

Base Class References and Derived Objects

As you know, C# is a strongly typed language. Aside from the standard conversions and automatic promotions that apply to its value types, type compatibility is strictly enforced. Therefore, a reference variable for one class type cannot normally refer to an object of another class type. For example, consider the following program that declares two classes that are identical in their composition:

// This program will not compile.

class X {
  int a;

  public X(int i) { a = i; }
}

class Y {
  int a;

  public Y(int i) { a = i; }
}
class IncompatibleRef {
 static void Main() {
   X x = new X(10);
   X x2;
   Y y = new Y(5);

   x2 = x; // OK, both of same type

   x2 = y; // Error, not of same type
  }
}

Here, even though class X and class Y are physically the same, it is not possible to assign a reference of type Y to a variable of type X because they have different types. Therefore, this line is incorrect because it causes a compile-time type mismatch:

x2 = y; // Error, not of same type

In general, an object reference variable can refer only to objects of its type.

There is, however, an important exception to C#’s strict type enforcement. A reference variable of a base class can be assigned a reference to an object of any class derived from that base class. This is legal because an instance of a derived type encapsulates an instance of the base type. Thus, a base class reference can refer to it. Here is an example:

// A base class reference can refer to a derived class object.

using System;

class X {
  public int a;

  public X(int i) {
    a = i;
  }
}

class Y : X {
  public int b;

  public Y(int i, int j) : base(j) {
    b = i;
  }
}

class BaseRef {
  static void Main() {
    X x = new X(10);
    X x2;
    Y y = new Y(5, 6);

    x2 = x; // OK, both of same type
    Console.WriteLine("x2.a: " + x2.a);

    x2 = y; // OK because Y is derived from X
    Console.WriteLine("x2.a: " + x2.a);
    // X references know only about X members
    x2.a = 19; // OK
//    x2.b = 27; // Error, X doesn't have a b member
  }
}

In this program, Y is derived from X. Now, the assignment

x2 = y; // OK because Y is derived from X

is permissible because a base class reference, x2 in this case, can refer to a derived class object (which is the object referred to by y).

It is important to understand that it is the type of the reference variable—not the type of the object that it refers to—that determines what members can be accessed. That is, when a reference to a derived class object is assigned to a base class reference variable, you will have access only to those parts of the object defined by the base class. This is why x2 can’t access b even when it refers to a Y object. This makes sense because the base class has no knowledge of what a derived class adds to it. This is why the last line of code in the program is commented out.

Although the preceding discussion may seem a bit esoteric, it has some important practical applications. One is described here. The other is discussed later in this chapter, when virtual methods are covered.

An important place where derived class references are assigned to base class variables is when constructors are called in a class hierarchy. As you know, it is common for a class to define a constructor that takes an object of its class as a parameter. This allows the class to construct a copy of an object. Classes derived from such a class can take advantage of this feature. For example, consider the following versions of TwoDShape and Triangle. Both add constructors that take an object as a parameter.

// Pass a derived class reference to a base class reference.

using System;

class TwoDShape {
  double pri_width;
  double pri_height;

  // Default constructor.
  public TwoDShape() {
    Width = Height = 0.0;
  }

  // Constructor for TwoDShape.
  public TwoDShape(double w, double h) {
    Width = w;
    Height = h;
  }

  // Construct object with equal width and height.
  public TwoDShape(double x) {
   Width = Height = x;
  }
  // Construct a copy of a TwoDShape object.
  public TwoDShape(TwoDShape ob) {
    Width = ob.Width;
    Height = ob.Height;
  }

  // Properties for Width and Height.
  public double Width {
     get { return pri_width; }
     set { pri_width = value < 0 ? -value : value; }
  }
  public double Height {
     get { return pri_height; }
     set { pri_height = value < 0 ? -value : value; }
  }
  public void ShowDim() {
    Console.WriteLine("Width and height are " +
                       Width + " and " + Height);
  }
}
// A derived class of TwoDShape for triangles.
class Triangle : TwoDShape {
  string Style;

 // A default constructor.
 public Triangle() {
   Style = "null";
 }

 // Constructor for Triangle.
 public Triangle(string s, double w, double h) : base(w, h) {
   Style = s;
 }

 // Construct an isosceles triangle.
 public Triangle(double x) : base(x) {
   Style = "isosceles";
 }

 // Construct a copy of a Triangle object.
 public Triangle(Triangle ob) : base(ob) {
   Style = ob.Style;
 }

 // Return area of triangle.
 public double Area() {
   return Width * Height / 2;
 }
 // Display a triangle's style.
 public void ShowStyle() {
   Console.WriteLine("Triangle is " + Style);
 }
}

class Shapes7 {
  static void Main() {
    Triangle t1 = new Triangle("right", 8.0, 12.0);

    // Make a copy of t1.
    Triangle t2 = new Triangle(t1);

    Console.WriteLine("Info for t1: ");
    t1.ShowStyle();
    t1.ShowDim();
    Console.WriteLine("Area is " + t1.Area());

    Console.WriteLine();

    Console.WriteLine("Info for t2: ");
    t2.ShowStyle();
    t2.ShowDim();
    Console.WriteLine("Area is " + t2.Area());
  }
}

In this program, t2 is constructed from t1 and is, thus, identical. The output is shown here:

Info for t1:
Triangle is right
Width and height are 8 and 12
Area is 48

Info for t2:
Triangle is right
Width and height are 8 and 12
Area is 48

Pay special attention to this Triangle constructor:

public Triangle(Triangle ob) : base(ob) {
  Style = ob.Style;
}

It receives an object of type Triangle, and it passes that object (through base) to this TwoDShape constructor:

public TwoDShape(TwoDShape ob) {
  Width = ob.Width;
  Height = ob.Height;
}

The key point is that TwoDShape( ) is expecting a TwoDShape object. However, Triangle( ) passes it a Triangle object. As explained, the reason this works is because a base class reference can refer to a derived class object. Thus, it is perfectly acceptable to pass TwoDShape( ) a reference to an object of a class derived from TwoDShape. Because the TwoDShape( ) constructor is initializing only those portions of the derived class object that are members of TwoDShape, it doesn’t matter that the object might also contain other members added by derived classes.

Virtual Methods and Overriding

A virtual method is a method that is declared as virtual in a base class. The defining characteristic of a virtual method is that it can be 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 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 can be 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 name, return type, and signature of the overriding 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 {
  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, 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 {
  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 {
  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, since 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. The same is true for indexers.

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 applying polymorphism successfully 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. Through virtual methods, it also defines those methods that the derived class can implement on its own. This allows the derived class flexibility, 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;
  double pri_height;

  // 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 a copy of a TwoDShape object.
 public TwoDShape(TwoDShape ob) {
   Width = ob.Width;
   Height = ob.Height;
   name = ob.name;
 }

 // Properties for Width and Height.
 public double Width {
    get { return pri_width; }
    set { pri_width = value < 0 ? -value : value; }
 }

 public double Height {
    get { return pri_height; }
    set { pri_height = value < 0 ? -value : value; }
 }
 public string name { get; set; }

 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;

  // 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 a copy of a Triangle 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 a copy of a Rectangle 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 {
  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. The type of object stored in a base class reference variable is determined at runtime and acted on accordingly. 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.

Using Abstract Classes

Sometimes you will want to create a base class that defines only a generalized form that will be shared by all of its derived classes, leaving it to each derived class to fill in the details. Such a class determines the nature of the methods that the derived classes must implement, but does not, itself, provide an implementation of one or more of these methods. One way this situation can occur is when a base class is unable to create a meaningful implementation for a method. This is the case with the version of TwoDShape used in the preceding example. The definition of Area( ) is simply a placeholder. It will not compute and display the area of any type of object.

You will see as you create your own class libraries that it is not uncommon for a method to have no meaningful definition in the context of its base class. You can handle this situation two ways. One way, as shown in the previous example, is to simply have it report a warning message. Although this approach can be useful in certain situations—such as debugging—it is not usually appropriate. You may have methods that must be overridden by the derived class in order for the derived class to have any meaning. Consider the class Triangle. It is incomplete if Area( ) is not defined. In such a case, you want some way to ensure that a derived class does, indeed, override all necessary methods. C#’s solution to this problem is the abstract method.

An abstract method is created by specifying the abstract type modifier. An abstract method contains no body and is, therefore, not implemented by the base class. Thus, a derived class must override it—it cannot simply use the version defined in the base class. As you can probably guess, an abstract method is automatically virtual, and there is no need to use the virtual modifier. In fact, it is an error to use virtual and abstract together.

To declare an abstract method, use this general form:

abstract type name(parameter-list);

As you can see, no method body is present. The abstract modifier can be used only on instance methods. It cannot be applied to static methods. Properties and indexers can also be abstract.

A class that contains one or more abstract methods must also be declared as abstract by preceding its class declaration with the abstract specifier. Since an abstract class does not define a complete implementation, there can be no objects of an abstract class. Thus, attempting to create an object of an abstract class by using new will result in a compile-time error.

When a derived class inherits an abstract class, it must implement all of the abstract methods in the base class. If it doesn’t, then the derived class must also be specified as abstract. Thus, the abstract attribute is inherited until such time as a complete implementation is achieved.

Using an abstract class, you can improve the TwoDShape class. Since there is no meaningful concept of area for an undefined two-dimensional figure, the following version of the preceding program declares Area( ) as abstract inside TwoDShape and TwoDShape as abstract. This, of course, means that all classes derived from TwoDShape must override Area( ).

// Create an abstract class.

using System;

abstract class TwoDShape {
  double pri_width;
  double pri_height;

  // 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 a copy of a TwoDShape object.
  public TwoDShape(TwoDShape ob) {
    Width = ob.Width;
    Height = ob.Height;
    name = ob.name;
  }

 // Properties for Width and Height.
 public double Width {
    get { return pri_width; }
    set { pri_width = value < 0 ? -value : value; }
 }
 public double Height {
    get { return pri_height; }
    set { pri_height = value < 0 ? -value : value; }
 }

 public string name { get; set; }

 public void ShowDim() {
   Console.WriteLine("Width and height are " +
                      Width + " and " + Height);
 }

 // Now, Area() is abstract.
 public abstract double Area();
}

// A derived class of TwoDShape for triangles.
class Triangle : TwoDShape {
  string Style;

 // 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 a copy of a Triangle 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 a copy of a Rectangle 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 AbsShape {
  static void Main() {
    TwoDShape[] shapes = new TwoDShape[4];

    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);

    for(int i=0; i < shapes.Length; i++) {
      Console.WriteLine("object is " + shapes[i].name);
      Console.WriteLine("Area is " + shapes[i].Area());

      Console.WriteLine();
    }
  }
}

As the program illustrates, all derived classes must override Area( ) (or also be declared abstract). To prove this to yourself, try creating a derived class that does not override Area( ). You will receive a compile-time error. Of course, it is still possible to create an object reference of type TwoDShape, which the program does. However, it is no longer possible to declare objects of type TwoDShape. Because of this, in Main( ) the shapes array has been shortened to 4, and a generic TwoDShape object is no longer created.

One other point: Notice that TwoDShape still includes the ShowDim( ) method and that it is not modified by abstract. It is perfectly acceptable—indeed, quite common—for an abstract class to contain concrete methods that a derived class is free to use as-is. Only those methods declared as abstract must be overridden by derived classes.

Using sealed to Prevent Inheritance

As powerful and useful as inheritance is, sometimes you will want to prevent it. For example, you might have a class that encapsulates the initialization sequence of some specialized hardware device, such as a medical monitor. In this case, you don’t want users of your class to be able to change the way the monitor is initialized, possibly setting the device incorrectly. Whatever the reason, in C# it is easy to prevent a class from being inherited by using the keyword sealed.

To prevent a class from being inherited, precede its declaration with sealed. As you might expect, it is illegal to declare a class as both abstract and sealed because an abstract class is incomplete by itself and relies upon its derived classes to provide complete implementations.

Here is an example of a sealed class:

sealed class A {
  // ...
}

// The following class is illegal.
class B : A { // ERROR! Can't derive from class A
  // ...
}

As the comments imply, it is illegal for B to inherit A because A is declared as sealed.

One other point: sealed can also be used on virtual methods to prevent further overrrides. For example, assume a base class called B and a derived class called D. A method declared virtual in B can be declared sealed by D. This would prevent any class that inherits D from overriding the method. This situation is illustrated by the following:

class B {
  public virtual void MyMethod() { /* ... */ }
}

class D : B {
  // This seals MyMethod() and prevents further overrides.
  sealed public override void MyMethod() { /* ... */ }
}

class X : D {
  // Error! MyMethod() is sealed!
  public override void MyMethod() { /* ... */ }
}

Because MyMethod( ) is sealed by D, it can’t be overridden by X.

The object Class

C# defines one special class called object that is an implicit base class of all other classes and for all other types (including the value types). In other words, all other types are derived from object. This means that a reference variable of type object can refer to an object of any other type. Also, since arrays are implemented as objects, a variable of type object can also refer to any array. Technically, the C# name object is just another name for System.Object, which is part of the .NET Framework class library.

The object class defines the methods shown in Table 11-1, which means that they are available in every object.

A few of these methods warrant some additional explanation. By default, the Equals(object) method determines if the invoking object refers to the same object as the one referred to by the argument. (That is, it determines if the two references are the same.) It returns true if the objects are the same, and false otherwise. You can override this method in classes that you create. Doing so allows you to define what equality means relative to a class. For example, you could define Equals(object) so that it compares the contents of two objects for equality.

The GetHashCode( ) method returns a hash code associated with the invoking object. A hash code is needed by any algorithm that employs hashing as a means of accessing stored objects. It is important to understand that the default implementation of GetHashCode( ) will not be adequate for all uses.

As mentioned in Chapter 9, if you overload the = = operator, then you will usually need to override Equals(object) and GetHashCode( ) because most of the time you will want the = = operator and the Equals(object) methods to function the same. When Equals( ) is overridden, you often need to override GetHashCode( ), so that the two methods are compatible.

The ToString( ) method returns a string that contains a description of the object on which it is called. Also, this method is automatically called when an object is output using WriteLine( ). Many classes override this method. Doing so allows them to tailor a description specifically for the types of objects that they create. For example:

Image

TABLE 11-1 Methods of the object Class

// Demonstrate ToString()

using System;

class MyClass {
  static int count = 0;
  int id;

  public MyClass() {
    id = count;
    count++;
 }

 public override string ToString() {
   return "MyClass object #" + id;
 }
}

class Test {
  static void Main() {
    MyClass ob1 = new MyClass();
    MyClass ob2 = new MyClass();
    MyClass ob3 = new MyClass();

    Console.WriteLine(ob1);
    Console.WriteLine(ob2);
    Console.WriteLine(ob3);
  }
}

The output from the program is shown here:

MyClass object #0
MyClass object #1
MyClass object #2

Boxing and Unboxing

As explained, all C# types, including the value types, are derived from object. Thus, a reference of type object can be used to refer to any other type, including value types. When an object reference refers to a value type, a process known as boxing occurs. Boxing causes the value of a value type to be stored in an object instance. Thus, a value type is “boxed” inside an object. This object can then be used like any other object. In all cases, boxing occurs automatically. You simply assign a value to an object reference. C# handles the rest.

Unboxing is the process of retrieving a value from a boxed object. This action is performed using an explicit cast from the object reference to its corresponding value type. Attempting to unbox an object into a different type will result in a runtime error.

Here is a simple example that illustrates boxing and unboxing:

// A simple boxing/unboxing example.

using System;

class BoxingDemo {
  static void Main() {
    int x;
    object obj;

    x = 10;
    obj = x; // box x into an object

    int y = (int)obj; // unbox obj into an int
    Console.WriteLine(y);
  }
}

This program displays the value 10. Notice that the value in x is boxed simply by assigning it to obj, which is an object reference. The integer value in obj is retrieved by casting obj to int.

Here is another, more interesting example of boxing. In this case, an int is passed as an argument to the Sqr( ) method, which uses an object parameter.

// Boxing also occurs when passing values.

using System;

class BoxingDemo {
  static void Main() {
    int x;

    x = 10;
    Console.WriteLine("Here is x: " + x);

    // x is automatically boxed when passed to Sqr().
    x = BoxingDemo.Sqr(x);
    Console.WriteLine("Here is x squared: " + x);
  }

  static int Sqr(object o) {
    return (int)o * (int)o;
  }
}

The output from the program is shown here:

Here is x: 10
Here is x squared: 100

Here, the value of x is automatically boxed when it is passed to Sqr( ).

Boxing and unboxing allow C#’s type system to be fully unified. All types derive from object. A reference to any type can be assigned to a variable of type object. Boxing and unboxing automatically handle the details for the value types. Furthermore, because all types are derived from object, they all have access to object’s methods. For example, consider the following rather surprising program:

// Boxing makes it possible to call methods on a value!

using System;

class MethOnValue {
  static void Main() {

    Console.WriteLine(10.ToString());
  }
}

This program displays 10. The reason is that the ToString( ) method returns a string representation of the object on which it is called. In this case, the string representation of 10 is 10!

Is object a Universal Data Type?

Given that object is a base class for all other types and that boxing of the value types takes place automatically, it is possible to use object as a “universal” data type. For example, consider the following program that creates an array of object and then assigns various other types of data to its elements:

// Use object to create a "generic" array.

using System;

class GenericDemo {
  static void Main() {
    object[] ga = new object[10];

    // Store ints.
    for(int i=0; i < 3; i++)
      ga[i] = i;

   // Store doubles.
   for(int i=3; i < 6; i++)
     ga[i] = (double) i / 2;

   // Store two strings, a bool, and a char.
   ga[6] = "Hello";
   ga[7] = true;
   ga[8] = 'X';
   ga[9] = "end";

   for(int i = 0; i < ga.Length; i++)
     Console.WriteLine("ga[" + i + "]: " + ga[i] + " ");
  }
}

The output is shown here:

ga[0]: 0
ga[1]: 1
ga[2]: 2
ga[3]: 1.5
ga[4]: 2
ga[5]: 2.5
ga[6]: Hello
ga[7]: True
ga[8]: X
ga[9]: end

As this program illustrates, because an object reference can hold a reference to any other type of data, it is possible to use an object reference to refer to any type of data. Thus, an array of object as used by the program can store any type of data. Expanding on this concept, it is easy to see how you could construct a stack class, for example, that stored object references. This would enable the stack to store any type of data.

Although the universal-type feature of object is powerful and can be used quite effectively in some situations, it is a mistake to think that you should use object as a way around C#’s otherwise strong type checking. In general, when you need to store an int, use an int variable; when you need to store a string, use a string reference; and so on.

More importantly, since version 2.0, true generic types are available to the C# programmer. (Generics are described in Chapter 18.) Generics enable you to easily define classes and algorithms that automatically work with different types of data in a type-safe manner. Because of generics, you will normally not need to use object as a universal type when creating new code. Today, it’s best to reserve object’s universal nature for specialized situations.