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 {   public 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 Y reference to an X object, 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. 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 {   public 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 situation 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 constructor. using System; class TwoDShape {   double pri_width;  // private   double pri_height; // private   // 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 object from an 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; }   }   public double height {      get { return pri_height; }      set { pri_height = 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.   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 an object from an 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 {   public 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:

 // Construct an object from an object. 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:

 // Construct object from an object. 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.




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