Section 11.4 created a commission-employee class hierarchy, in which class BasePlusCommissionEmployee
inherited from class CommissionEmployee
. The examples in that section manipulated CommissionEmployee
and BasePlusCommissionEmployee
objects by using references to them to invoke their methods. We aimed base-class references at base-class objects and derived-class references at derived-class objects. These assignments are natural and straightforward—base-class references are intended to refer to base-class objects, and derived-class references are intended to refer to derived-class objects. However, other assignments are possible.
The next example aims a base-class reference at a derived-class object, then shows how invoking a method on a derived-class object via a base-class reference invokes the derived-class functionality—the type of the actual referenced object, not the type of the reference, determines which method is called. This demonstrates the key concept that a derived-class object can be treated as an object of its base class, which enables various interesting manipulations. An app can create a collection of base-class references that refer to objects of many derived-class types, because each derived-class object is an object of its base class. For instance, we can assign the reference of a BasePlusCommissionEmployee
object to a base-class Commission-Employee
variable because a BasePlusCommissionEmployee
is a CommissionEmployee
—so we can treat a BasePlusCommissionEmployee
as a CommissionEmployee
.
A base-class object is not an object of any of its derived classes. For example, we cannot directly assign the reference of a CommissionEmployee
object to a derived-class Base-PlusCommissionEmployee
variable, because a CommissionEmployee
is not a BasePlusCommissionEmployee
—a CommissionEmployee
does not, for example, have a baseSalary
instance variable and does not have a BaseSalary
property. The compiler allows the assignment of a base-class reference to a derived-class variable if we explicitly cast the base-class reference to the derived-class type—a technique we discuss in greater detail in Section 12.5.6.
The is-a relationship applies from a derived class to its direct and indirect base classes, but not vice versa.
Figure 12.1 demonstrates three ways to use base-class and derived-class variables to store references to base-class and derived-class objects. The first two are straightforward— as in Section 11.4, we assign a base-class reference to a base-class variable, and we assign a derived-class reference to a derived-class variable. Then we demonstrate the relationship between derived classes and base classes (i.e., the is-a relationship) by assigning a derived-class reference to a base-class variable. [Note: This app uses classes CommissionEmployee
and BasePlusCommissionEmployee
from Fig. 11.10 and Fig. 11.11, respectively.]
In Fig. 12.1, lines 11–12 create a new CommissionEmployee
object and assign its reference to a CommissionEmployee
variable and lines 15–16 create a new BasePlusCommissionEmployee
object and assign its reference to a BasePlusCommissionEmployee
variable. These assignments are natural—a CommissionEmployee
variable’s primary purpose is to hold a reference to a CommissionEmployee
object. Lines 23–24 use the reference commissionEmployee
to invoke methods ToString
and Earnings
. Because commissionEmployee
refers to a CommissionEmployee
object, base class CommissionEmployee
’s version of the methods are called. Similarly, lines 31–33 use the reference basePlusCommissionEmployee
to invoke the methods ToString
and Earnings
on the BasePlusCommissionEmployee
object. This invokes derived class BasePlusCommissionEmployee
’s version of the methods.
Line 37 then assigns the reference to derived-class object basePlusCommissionEmployee
to a base-class CommissionEmployee
variable, which lines 41–43 use to invoke methods ToString
and Earnings
. Note that the call commissionEmployee2.ToString()
in line 41 actually calls derived class BasePlusCommissionEmployee
’s ToString
method. The compiler allows this “crossover” because an object of a derived class is an object of its base class (but not vice versa). When the compiler encounters a virtual
method call made through a variable, the compiler checks the variable’s class type to determine if the method can be called. If that class contains the proper method declaration (or inherits one), the call compiles. At execution time, the type of the object to which the variable refers determines the actual method to use.
A base-class variable that contains a reference to a derived-class object and is used to call a virtual
method actually calls the overriding derived-class version of the method.