Chapter 12. OOP: Polymorphism, Interfaces and Operator Overloading

 

One Ring to rule them all, One Ring to find them, One Ring to bring them all and in the darkness bind them.

 
 --John Ronald Reuel Tolkien
 

General propositions do not decide concrete cases.

 
 --Oliver Wendell Holmes
 

A philosopher of imposing stature doesn’t think in a vacuum. Even his most abstract ideas are, to some extent, conditioned by what is or is not known in the time when he lives.

 
 --Alfred North Whitehead
<feature> <supertitle>Objectives</supertitle>

In this chapter you’ll learn:

<objective>

How polymorphism enables you to “program in the general” and make systems extensible.

</objective>
<objective>

To use overridden methods to effect polymorphism.

</objective>
<objective>

To create abstract classes and methods.

</objective>
<objective>

To determine an object’s type at execution time.

</objective>
<objective>

To create sealed methods and classes.

</objective>
<objective>

To declare and implement interfaces.

</objective>
<objective>

To overload operators to enable them to manipulate objects.

</objective>
</feature>
<feature> <supertitle>Outline</supertitle> </feature>

Introduction

We now continue our study of object-oriented programming by explaining and demonstrating polymorphism with inheritance hierarchies. Polymorphism enables us to “program in the general” rather than “program in the specific.” In particular, polymorphism enables us to write applications that process objects that share the same base class in a class hierarchy as if they were all objects of the base class.

Let’s consider a polymorphism example. Suppose we create an application that simulates moving several types of animals for a biological study. Classes Fish, Frog and Bird represent the types of animals under investigation. Imagine that each class extends base class Animal, which contains a method Move and maintains an animal’s current location as xyz coordinates. Each derived class implements method Move. Our application maintains an array of references to objects of the various Animal-derived classes. To simulate an animal’s movements, the application sends each object the same message once per second—namely, Move. Each specific type of Animal responds to a Move message in a unique way—a Fish might swim three feet, a Frog might jump five feet and a Bird might fly 10 feet. The application issues the Move message to each animal object generically, but each object modifies its xyz coordinates appropriately for its specific type of movement. Relying on each object to know how to “do the right thing” in response to the same method call is the key concept of polymorphism. The same message (in this case, Move) sent to a variety of objects has “many forms” of results—hence the term polymorphism.

Systems Are Easy to Extend

With polymorphism, we can design and implement systems that are easily extensible—new classes can be added with little or no modification to the general portions of the application, as long as the new classes are part of the inheritance hierarchy that the application processes generically. The only parts of an application that must be altered to accommodate new classes are those that require direct knowledge of the new classes that you add to the hierarchy. For example, if we extend class Animal to create class Tortoise (which might respond to a Move message by crawling one inch), we need to write only the Tortoise class and the part of the simulation that instantiates a Tortoise object. The portions of the simulation that process each Animal generically can remain the same.

This chapter has several parts. First, we discuss common examples of polymorphism. We then provide a live-code example demonstrating polymorphic behavior. As you’ll soon see, you’ll use base-class references to manipulate both base-class objects and derived-class objects polymorphically.

Polymorphic Employee Inheritance Hierarchy

We then present a case study that revisits the employee hierarchy of Section 11.4.5. We develop a simple payroll application that polymorphically calculates the weekly pay of several different types of employees using each employee’s Earnings method. Though the earnings of each type of employee are calculated in a specific way, polymorphism allows us to process the employees “in the general.” In the case study, we enlarge the hierarchy to include two new classes—SalariedEmployee (for people paid a fixed weekly salary) and HourlyEmployee (for people paid an hourly salary and “time-and-a-half” for overtime). We declare a common set of functionality for all the classes in the updated hierarchy in an “abstract” class, Employee, from which classes SalariedEmployee, HourlyEmployee and CommissionEmployee inherit directly and class BasePlusCommissionEmployee inherits indirectly. As you’ll soon see, when we invoke each employee’s Earnings method off a baseclass Employee reference, the correct earnings calculation is performed due to C#’s polymorphic capabilities.

Determining the Type of an Object at Execution Time

Occasionally, when performing polymorphic processing, we need to program “in the specific.” Our Employee case study demonstrates that an application can determine the type of an object at execution time and act on that object accordingly. In the case study, we use these capabilities to determine whether a particular employee object is a BasePlusCommissionEmployee. If so, we increase that employee’s base salary by 10%.

Interfaces

The chapter continues with an introduction to C# interfaces. An interface describes a set of methods and properties that can be called on an object, but does not provide concrete implementations for them. You can declare classes that implement (i.e., provide concrete implementations for the methods and properties of) one or more interfaces. Each interface member must be defined for all the classes that implement the interface. Once a class implements an interface, all objects of that class have an is-a relationship with the interface type, and all objects of the class are guaranteed to provide the functionality described by the interface. This is true of all derived classes of that class as well.

Interfaces are particularly useful for assigning common functionality to possibly unrelated classes. This allows objects of unrelated classes to be processed polymorphically—objects of classes that implement the same interface can respond to the same method calls. To demonstrate creating and using interfaces, we modify our payroll application to create a general accounts-payable application that can calculate payments due for the earnings of company employees and for invoice amounts to be billed for purchased goods. As you’ll see, interfaces enable polymorphic capabilities similar to those enabled by inheritance.

Operator Overloading

This chapter ends with an introduction to operator overloading. In previous chapters, we declared our own classes and used methods to perform tasks on objects of those classes. Operator overloading allows us to define the behavior of the built-in operators, such as +, - and <, when used on objects of our own classes. This provides a much more convenient notation than calling methods for performing tasks on objects.

Polymorphism Examples

We now consider several additional examples of polymorphism.

Quadrilateral Inheritance Hierachy

If class Rectangle is derived from class Quadrilateral (a four-sided shape), then a Rectangle is a more specific version of a Quadrilateral. Any operation (e.g., calculating the perimeter or the area) that can be performed on a Quadrilateral object can also be performed on a Rectangle object. These operations also can be performed on other Quadrilaterals, such as Squares, Parallelograms and Trapezoids. The polymorphism occurs when an application invokes a method through a base-class variable—at execution time, the correct derived-class version of the method is called, based on the type of the referenced object. You’ll see a simple code example that illustrates this process in Section 12.3.

Video Game SpaceObject Inheritance Hierarchy

As another example, suppose we design a video game that manipulates objects of many different types, including objects of classes Martian, Venusian, Plutonian, SpaceShip and LaserBeam. Imagine that each class inherits from the common base class SpaceObject, which contains method Draw. Each derived class implements this method. A screen-manager application maintains a collection (e.g., a SpaceObject array) of references to objects of the various classes. To refresh the screen, the screen manager periodically sends each object the same message—namely, Draw. However, each object responds in a unique way. For example, a Martian object might draw itself in red with the appropriate number of antennae. A SpaceShip object might draw itself as a bright silver flying saucer. A LaserBeam object might draw itself as a bright red beam across the screen. Again, the same message (in this case, Draw) sent to a variety of objects has many forms of results.

A polymorphic screen manager might use polymorphism to facilitate adding new classes to a system with minimal modifications to the system’s code. Suppose we want to add Mercurian objects to our video game. To do so, we must build a Mercurian class that extends SpaceObject and provides its own Draw method implementation. When objects of class Mercurian appear in the SpaceObject collection, the screen-manager code invokes method Draw, exactly as it does for every other object in the collection, regardless of its type, so the new Mercurian objects simply “plug right in” without any modification of the screen-manager code by the programmer. Thus, without modifying the system (other than to build new classes and modify the code that creates new objects), you can use polymorphism to include additional types that might not have been envisioned when the system was created.

Software Engineering Observation 12.1

Software Engineering Observation 12.1

Polymorphism promotes extensibility: Software that invokes polymorphic behavior is independent of the object types to which messages are sent. New object types that can respond to existing method calls can be incorporated into a system without requiring modification of the base system. Only client code that instantiates new objects must be modified to accommodate new types.

Demonstrating Polymorphic Behavior

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.

In the next example, we aim a base-class reference at a derived-class object. We then show how invoking a method on a derived-class object via a base-class reference can invoke the derived-class functionality—the type of the actual referenced object, not the type of the reference, determines which method is called. This example demonstrates the key concept that an object of a derived class can be treated as an object of its base class. This enables various interesting manipulations. An application can create an array of base-class references that refer to objects of many derived-class types. This is allowed 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 CommissionEmployee 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 BasePlusCommissionEmployee 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 is-a relationship applies from a derived class to its direct and indirect base classes, but not vice versa.

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. Why would we ever want to perform such an assignment? A base-class reference can be used to invoke only the methods declared in the base class—attempting to invoke derived-class-only methods through a base-class reference results in compilation errors. If an application needs to perform a derived-class-specific operation on a derived-class object referenced by a base-class variable, the application must first cast the base-class reference to a derived-class reference through a technique known as downcasting. This enables the application to invoke derived-class methods that are not in the base class. We present an example of downcasting in Section 12.5.6.

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 derivedclass reference to a base-class variable. [Note: This application uses classes CommissionEmployee and BasePlusCommissionEmployee from Fig. 11.12 and Fig. 11.13, respectively.]

Example 12.1. Assigning base-class and derived-class references to base-class and derived-class variables.

 1   // Fig. 12.1: PolymorphismTest.cs
 2   // Assigning base-class and derived-class references to base-class and
 3   // derived-class variables.
 4   using System;
 5
 6   public class PolymorphismTest
 7   {
 8      public static void Main( string[] args )
 9      {
10         // assign base-class reference to base-class variable
11         CommissionEmployee commissionEmployee = new CommissionEmployee(
12            "Sue", "Jones", "222-22-2222", 10000.00M, .06M );           
13
14         // assign derived-class reference to derived-class variable
15         BasePlusCommissionEmployee basePlusCommissionEmployee =
16            new BasePlusCommissionEmployee( "Bob", "Lewis",     
17            "333-33-3333", 5000.00M, .04M, 300.00M );           
18
19         // invoke ToString and Earnings on base-class object
20         // using base-class variable
21         Console.WriteLine( "{0} {1}:

{2}
{3}: {4:C}
",
22            "Call CommissionEmployee's ToString and Earnings methods ",
23            "with base-class reference to base class object",
24            commissionEmployee.ToString(),
25            "earnings", commissionEmployee.Earnings() );
26
27         // invoke ToString and Earnings on derived-class object
28         // using derived-class variable
29         Console.WriteLine( "{0} {1}:

{2}
{3}: {4:C}
",
30            "Call BasePlusCommissionEmployee's ToString and Earnings ",
31            "methods with derived class reference to derived-class object",
32            basePlusCommissionEmployee.ToString(),
33            "earnings", basePlusCommissionEmployee.Earnings() );
34
35         // invoke ToString and Earnings on derived-class object
36         // using base-class variable
37         CommissionEmployee commissionEmployee2 =
38            basePlusCommissionEmployee;          
39         Console.WriteLine( "{0} {1}:

{2}
{3}: {4:C}",
40            "Call BasePlusCommissionEmployee's ToString and Earnings ",
41            "with base class reference to derived-class object",
42            commissionEmployee2.ToString(), "earnings",
43            commissionEmployee2.Earnings() );
44         } // end Main
45      } // end class PolymorphismTest
Call CommissionEmployee's ToString and Earnings methods with base class reference to base class object:

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: $10,000.00
commission rate: 0.06
earnings: $600.00

Call BasePlusCommissionEmployee's ToString and Earnings methods with derived
class reference to derived class object:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: $5,000.00
commission rate: 0.04
base salary: $300.00
earnings: $500.00

Call BasePlusCommissionEmployee's ToString and Earnings methods with base
class reference to derived class object:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: $5,000.00
commission rate: 0.04
base salary: $300.00
earnings: $500.00

In Fig. 12.1, lines 11–12 create a new CommissionEmployee object and assign its reference to a CommissionEmployee variable. Lines 15–17 create a new BasePlusCommissionEmployee object and assign its reference to a BasePlusCommissionEmployee variable. These assignments are natural—for example, a CommissionEmployee variable’s primary purpose is to hold a reference to a CommissionEmployee object. Lines 21–25 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 29–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.

Lines 37–38 then assign the reference to derived-class object basePlusCommissionEmployee to a base-class CommissionEmployee variable, which lines 39–43 use to invoke methods ToString and Earnings. A base-class variable that contains a reference to a derivedclass object and is used to call a virtual method actually calls the overriding derived-class version of the method. Hence, commissionEmployee2.ToString() in line 42 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 method call made through a variable, the compiler determines if the method can be called by checking the variable’s class type. If that class contains the proper method declaration (or inherits one), the compiler allows the call to be compiled. At execution time, the type of the object to which the variable refers determines the actual method to use.

Abstract Classes and Methods

When we think of a class type, we assume that applications will create objects of that type. In some cases, however, it’s useful to declare classes for which you never intend to instantiate objects. Such classes are called abstract classes. Because they’re used only as base classes in inheritance hierarchies, we refer to them as abstract base classes. These classes cannot be used to instantiate objects, because, as you’ll soon see, abstract classes are incomplete—derived classes must define the “missing pieces.” We demonstrate abstract classes in Section 12.5.1.

The purpose of an abstract class is primarily to provide an appropriate base class from which other classes can inherit, and thus share a common design. In the Shape hierarchy of Fig. 11.3, for example, derived classes inherit the notion of what it means to be a Shape—common attributes such as location, color and borderThickness, and behaviors such as Draw, Move, Resize and ChangeColor. Classes that can be used to instantiate objects are called concrete classes. Such classes provide implementations of every method they declare (some of the implementations can be inherited). For example, we could derive concrete classes Circle, Square and Triangle from abstract base class TwoDimensionalShape. Similarly, we could derive concrete classes Sphere, Cube and Tetrahedron from abstract base class ThreeDimensionalShape. Abstract base classes are too general to create real objects—they specify only what is common among derived classes. We need to be more specific before we can create objects. For example, if you send the Draw message to abstract class TwoDimensionalShape, the class knows that two-dimensional shapes should be drawable, but it does not know what specific shape to draw, so it cannot implement a real Draw method. Concrete classes provide the specifics that make it reasonable to instantiate objects.

Not all inheritance hierarchies contain abstract classes. However, you’ll often write client code that uses only abstract base-class types to reduce client code’s dependencies on a range of specific derived-class types. For example, you can write a method with a parameter of an abstract base-class type. When called, such a method can be passed an object of any concrete class that directly or indirectly extends the base class specified as the parameter’s type.

Abstract classes sometimes constitute several levels of the hierarchy. For example, the Shape hierarchy of Fig. 11.3 begins with abstract class Shape. On the next level of the hierarchy are two more abstract classes, TwoDimensionalShape and ThreeDimensionalShape. The next level of the hierarchy declares concrete classes for TwoDimensionalShapes (Circle, Square and Triangle) and for ThreeDimensionalShapes (Sphere, Cube and Tetrahedron).

You make a class abstract by declaring it with the keyword abstract. An abstract class normally contains one or more abstract methods. An abstract method is one with keyword abstract in its declaration, as in

public abstract void Draw(); // abstract method

Abstract methods are implicitly virtual and do not provide implementations. A class that contains abstract methods must be declared as an abstract class even if it contains some concrete (nonabstract) methods. Each concrete derived class of an abstract base class also must provide concrete implementations of the base class’s abstract methods. We show an example of an abstract class with an abstract method in Fig. 12.4.

Properties can also be declared abstract or virtual, then overridden in derived classes with the override keyword, just like methods. This allows an abstract base class to specify common properties of its derived classes. Abstract property declarations have the form:

public abstract PropertyType MyProperty
{
   get;
   set;
} // end abstract property

The semicolons after the get and set keywords indicate that we provide no implementation for these accessors. An abstract property may omit implementations for the get accessor or the set accessor. Concrete derived classes must provide implementations for every accessor declared in the abstract property. When both get and set accessors are specified, every concrete derived class must implement both. If one accessor is omitted, the derived class is not allowed to implement that accessor. Doing so causes a compilation error.

Constructors and static methods cannot be declared abstract. Constructors are not inherited, so an abstract constructor could never be implemented. Similarly, derived classes cannot override static methods, so an abstract static method could never be implemented.

Software Engineering Observation 12.2

Software Engineering Observation 12.2

An abstract class declares common attributes and behaviors of the various classes that inherit from it, either directly or indirectly, in a class hierarchy. An abstract class typically contains one or more abstract methods or properties that concrete derived classes must override. The instance variables, concrete methods and concrete properties of an abstract class are subject to the normal rules of inheritance.

Common Programming Error 12.1

Common Programming Error 12.1

Attempting to instantiate an object of an abstract class is a compilation error.

Common Programming Error 12.2

Common Programming Error 12.2

Failure to implement a base class’s abstract methods and properties in a derived class is a compilation error unless the derived class is also declared abstract

Although we cannot instantiate objects of abstract base classes, you’ll soon see that we can use abstract base classes to declare variables that can hold references to objects of any concrete classes derived from those abstract classes. Applications typically use such variables to manipulate derived-class objects polymorphically. Also, you can use abstract baseclass names to invoke static methods declared in those abstract base classes.

Polymorphism and Device Drivers

Polymorphism is particularly effective for implementing so-called layered software systems. In operating systems, for example, each type of physical device could operate quite differently from the others. Even so, common commands can read or write data from and to the devices. For each device, the operating system uses a piece of software called a device driver to control all communication between the system and the device. The write message sent to a device driver object needs to be interpreted specifically in the context of that driver and how it manipulates a specific device. However, the write call itself really is no different from the write to any other device in the system: Place some number of bytes from memory onto that device. An object-oriented operating system might use an abstract base class to provide an “interface” appropriate for all device drivers. Then, through inheritance from that abstract base class, derived classes are formed that all behave similarly. The device-driver methods are declared as abstract methods in the abstract base class. The implementations of these abstract methods are provided in the derived classes that correspond to the specific types of device drivers. New devices are always being developed, often long after the operating system has been released. When you buy a new device, it comes with a device driver provided by the device vendor. The device is immediately operational after you connect it to your computer and install the device driver. This is another elegant example of how polymorphism makes systems extensible.

Iterators

It’s common in object-oriented programming to declare an iterator class that can traverse all the objects in a collection, such as an array (Chapter 8) or a List (Chapter 9). For example, an application can print a List of objects by creating an iterator object and using it to obtain the next list element each time the iterator is called. Iterators often are used in polymorphic programming to traverse a collection that contains references to objects of various classes in an inheritance hierarchy. (Chapters 2223 present a thorough treatment of C#’s “generics” capabilities and iterators.) A List of references to objects of class TwoDimensionalShape, for example, could contain references to objects from derived classes Square, Circle, Triangle and so on. Calling method Draw for each TwoDimensionalShape object off a TwoDimensionalShape variable would polymorphically draw each object correctly on the screen.

Case Study: Payroll System Using Polymorphism

This section reexamines the CommissionEmployee-BasePlusCommissionEmployee hierarchy that we explored throughout Section 11.4. Now we use an abstract method and polymorphism to perform payroll calculations based on the type of employee. We create an enhanced employee hierarchy to solve the following problem:

A company pays its employees on a weekly basis. The employees are of four types: Salaried employees are paid a fixed weekly salary regardless of the number of hours worked, hourly employees are paid by the hour and receive “time-and-a-half” overtime pay for all hours worked in excess of 40 hours, commission employees are paid a percentage of their sales, and salaried-commission employees receive a base salary plus a percentage of their sales. For the current pay period, the company has decided to reward salaried-commission employees by adding 10% to their base salaries. The company wants to implement a C# application that performs its payroll calculations polymorphically.

We use abstract class Employee to represent the general concept of an employee. The classes that extend Employee are SalariedEmployee, CommissionEmployee and HourlyEmployee. Class BasePlusCommissionEmployee—which extends CommissionEmployee—represents the last employee type. The UML class diagram in Fig. 12.2 shows the inheritance hierarchy for our polymorphic employee payroll application. Abstract class Employee is italicized, as per the convention of the UML.

Employee hierarchy UML class diagram.

Figure 12.2. Employee hierarchy UML class diagram.

Abstract base class Employee declares the “interface” to the hierarchy—that is, the set of methods that an application can invoke on all Employee objects. We use the term “interface” here in a general sense to refer to the various ways applications can communicate with objects of any Employee derived class. Be careful not to confuse the general notion of an “interface” with the formal notion of a C# interface, the subject of Section 12.7. Each employee, regardless of the way his or her earnings are calculated, has a first name, a last name and a social security number, so those pieces of data appear in abstract base class Employee.

Software Engineering Observation 12.3

Software Engineering Observation 12.3

A derived class can inherit “interface” or “implementation” from a base class. Hierarchies designed for implementation inheritance tend to have their functionality high in the hierarchy—each new derived class inherits one or more methods that were implemented in a base class, and the derived class uses the base-class implementations. Hierarchies designed for interface inheritance tend to have their functionality lower in the hierarchy—a base class specifies one or more abstract methods that must be declared for each concrete class in the hierarchy, and the individual derived classes override these methods to provide derived-class-specific implementations.

The following sections implement the Employee class hierarchy. The first section implements abstract base class Employee. The next four sections each implement one of the concrete classes. The sixth section implements a test application that builds objects of all these classes and processes those objects polymorphically.

Creating Abstract Base Class Employee

Class Employee (Fig. 12.4) provides methods Earnings and ToString, in addition to the auto-implemented properties that manipulate Employee’s data. An Earnings method certainly applies generically to all employees. But each earnings calculation depends on the employee’s class. So we declare Earnings as abstract in base class Employee, because a default implementation does not make sense for that method—there’s not enough information to determine what amount Earnings should return. Each derived class overrides Earnings with an appropriate implementation. To calculate an employee’s earnings, the application assigns a reference to the employee’s object to a base class Employee variable, then invokes the Earnings method on that variable. We maintain an array of Employee variables, each of which holds a reference to an Employee object (of course, there cannot be Employee objects because Employee is an abstract class—because of inheritance, however, all objects of all derived classes of Employee may nevertheless be thought of as Employee objects). The application iterates through the array and calls method Earnings for each Employee object. C# processes these method calls polymorphically. Including Earnings as an abstract method in Employee forces every directly derived concrete class of Employee to override Earnings with a method that performs an appropriate pay calculation.

Method ToString in class Employee returns a string containing the employee’s first name, last name and social security number. Each derived class of Employee overrides method ToString to create a string representation of an object of that class containing the employee’s type (e.g., "salaried employee:"), followed by the rest of the employee’s information.

The diagram in Fig. 12.3 shows each of the five classes in the hierarchy down the left side and methods Earnings and ToString across the top. For each class, the diagram shows the desired results of each method. [Note: We do not list base class Employee’s properties because they’re not overridden in any of the derived classes—each of these properties is inherited and used “as is” by each of the derived classes.]

Polymorphic interface for the Employee hierarchy classes.

Figure 12.3. Polymorphic interface for the Employee hierarchy classes.

Let’s consider class Employee’s declaration (Fig. 12.4). The class includes a constructor that takes the first name, last name and social security number as arguments (lines 15–20); read-only properties for obtaining the first name, last name and social security number (lines 6, 9 and 12, respectively); method ToString (lines 23–27), which uses properties to return the string representation of the Employee; and abstract method Earnings (line 30), which must be implemented by concrete derived classes. The Employee constructor does not validate the social security number in this example. Normally, such validation should be provided.

Example 12.4. Employee abstract base class.

 1   // Fig. 12.4: Employee.cs
 2   // Employee abstract base class.
 3   public abstract class Employee
 4   {
 5      // read-only property that gets employee's first name
 6      public string FirstName { get; private set; }
 7
 8      // read-only property that gets employee's last name
 9      public string LastName { get; private set; }
10
11      // read-only property that gets employee's social security number
12      public string SocialSecurityNumber { get; private set; }
13
14      // three-parameter constructor
15      public Employee( string first, string last, string ssn )
16      {
17         FirstName = first;
18         LastName = last;
19         SocialSecurityNumber = ssn;
20      } // end three-parameter Employee constructor
21
22      // return string representation of Employee object, using properties
23      public override string ToString()
24      {
25         return string.Format( "{0} {1}
social security number: {2}",
26            FirstName, LastName, SocialSecurityNumber );
27      } // end method ToString
28
29      // abstract method overridden by derived classes
30      public abstract decimal Earnings(); // no implementation here
31   } // end abstract class Employee

Why did we declare Earnings as an abstract method? As explained earlier, it simply does not make sense to provide an implementation of this method in class Employee. We cannot calculate the earnings for a general Employee—we first must know the specific Employee type to determine the appropriate earnings calculation. By declaring this method abstract, we indicate that each concrete derived class must provide an appropriate Earnings implementation and that an application will be able to use base-class Employee variables to invoke method Earnings polymorphically for any type of Employee.

Creating Concrete Derived Class SalariedEmployee

Class SalariedEmployee (Fig. 12.5) extends class Employee (line 5) and overrides Earnings (lines 34–37), which makes SalariedEmployee a concrete class. The class includes a constructor (lines 10–14) that takes a first name, a last name, a social security number and a weekly salary as arguments; property WeeklySalary (lines 17–31) to manipulate instance variable weeklySalary, including a set accessor that ensures we assign only nonnegative values to weeklySalary; method Earnings (lines 34–37) to calculate a SalariedEmployee’s earnings; and method ToString (lines 40–44), which returns a string including the employee’s type, namely, "salaried employee:", followed by employee-specific information produced by base class Employee’s ToString method and SalariedEmployee’s WeeklySalary property. Class SalariedEmployee’s constructor passes the first name, last name and social security number to the Employee constructor (line 11) via a constructor initializer to initialize the base class’s data. Method Earnings overrides Employee’s abstract method Earnings to provide a concrete implementation that returns the SalariedEmployee’s weekly salary. If we do not implement Earnings, class SalariedEmployee must be declared abstract—otherwise, a compilation error occurs (and, of course, we want SalariedEmployee to be a concrete class).

Example 12.5. SalariedEmployee class that extends Employee.

 1   // Fig. 12.5: SalariedEmployee.cs
 2   // SalariedEmployee class that extends Employee.
 3   using System;
 4
 5   public class SalariedEmployee : Employee
 6   {
 7      private decimal weeklySalary;
 8
 9      // four-parameter constructor
10      public SalariedEmployee( string first, string last, string ssn,
11         decimal salary ) : base( first, last, ssn )
12      {
13         WeeklySalary = salary; // validate salary via property
14      } // end four-parameter SalariedEmployee constructor
15
16      // property that gets and sets salaried employee's salary
17      public decimal WeeklySalary
18      {
19         get
20         {
21            return weeklySalary;
22         } // end get
23         set
24         {
25            if ( value >= 0 ) // validation
26               weeklySalary = value;
27            else
28               throw new ArgumentOutOfRangeException( "WeeklySalary",
29                  value, "WeeklySalary must be >= 0" );
30         } // end set
31      } // end property WeeklySalary
32
33      // calculate earnings; override abstract method Earnings in Employee
34      public override decimal Earnings()
35      {                                 
36         return WeeklySalary;           
37      } // end method Earnings          
38
39      // return string representation of SalariedEmployee object
40      public override string ToString()                             
41      {                                                             
42         return string.Format( "salaried employee: {0}
{1}: {2:C}",
43            base.ToString(), "weekly salary", WeeklySalary );       
44      } // end method ToString                                      
45   } // end class SalariedEmployee

SalariedEmployee method ToString (lines 40–44) overrides Employee’s version. If class SalariedEmployee did not override ToString, SalariedEmployee would have inherited the Employee version. In that case, SalariedEmployee’s ToString method would simply return the employee’s full name and social security number, which does not adequately represent a SalariedEmployee. To produce a complete string representation of a SalariedEmployee, the derived class’s ToString method returns "salaried employee: ", followed by the base-class Employee-specific information (i.e., first name, last name and social security number) obtained by invoking the base class’s ToString (line 43)—this is a nice example of code reuse. The string representation of a SalariedEmployee also contains the employee’s weekly salary, obtained by using the class’s WeeklySalary property.

Creating Concrete Derived Class HourlyEmployee

Class HourlyEmployee (Fig. 12.6) also extends class Employee (line 5). The class includes a constructor (lines 11–17) that takes as arguments a first name, a last name, a social security number, an hourly wage and the number of hours worked. Lines 20–34 and 37–51 declare properties Wage and Hours for instance variables wage and hours, respectively. The set accessor in property Wage ensures that wage is nonnegative, and the set accessor in property Hours ensures that hours is in the range 0168 (the total number of hours in a week) inclusive. The class overrides method Earnings (lines 54–60) to calculate an HourlyEmployee’s earnings and method ToString (lines 63–68) to return the employee’s string representation. The HourlyEmployee constructor, similarly to the SalariedEmployee constructor, passes the first name, last name and social security number to the base-class Employee constructor (line 13) to initialize the base class’s data. Also, method ToString calls base-class method ToString (line 67) to obtain the Employee-specific information (i.e., first name, last name and social security number.

Example 12.6. HourlyEmployee class that extends Employee.

 1   // Fig. 12.6: HourlyEmployee.cs
 2   // HourlyEmployee class that extends Employee.
 3   using System;
 4
 5   public class HourlyEmployee : Employee
 6   {
 7      private decimal wage; // wage per hour
 8      private decimal hours; // hours worked for the week
 9
10      // five-parameter constructor
11      public HourlyEmployee( string first, string last, string ssn,
12         decimal hourlyWage, decimal hoursWorked )
13         : base( first, last, ssn )
14      {
15         Wage = hourlyWage; // validate hourly wage via property
16         Hours = hoursWorked; // validate hours worked via property
17      } // end five-parameter HourlyEmployee constructor
18
19      // property that gets and sets hourly employee's wage
20      public decimal Wage
21      {
22         get
23         {
24            return wage;
25         } // end get
26         set
27         {
28            if ( value >= 0 ) // validation
29               wage = value;
30            else
31               throw new ArgumentOutOfRangeException( "Wage",
32                  value, "Wage must be >= 0" );
33         } // end set
34      } // end property Wage
35
36      // property that gets and sets hourly employee's hours
37      public decimal Hours
38      {
39         get
40         {
41            return hours;
42         } // end get
43         set
44         {
45            if ( value >= 0 && value <= 168 ) // validation
46               hours = value;
47            else
48               throw new ArgumentOutOfRangeException( "Hours",
49                  value, "Hours must be >= 0 and <= 168" );
50         } // end set
51      } // end property Hours
52
53      // calculate earnings; override Employee's abstract method Earnings
54      public override decimal Earnings()                            
55      {                                                             
56         if ( Hours <= 40 ) // no overtime                          
57            return Wage * Hours;                                    
58         else                                                       
59            return ( 40 * Wage ) + ( ( Hours - 40 ) * Wage * 1.5M );
60      } // end method Earnings                                      
61
62      // return string representation of HourlyEmployee object
63      public override string ToString()                                   
64      {                                                                   
65         return string.Format(                                            
66            "hourly employee: {0}
{1}: {2:C}; {3}: {4:F2}",              
67            base.ToString(), "hourly wage", Wage, "hours worked", Hours );
68      } // end method ToString                                            
69   } // end class HourlyEmployee

Creating Concrete Derived Class CommissionEmployee

Class CommissionEmployee (Fig. 12.7) extends class Employee (line 5). The class includes a constructor (lines 11–16) that takes a first name, a last name, a social security number, a sales amount and a commission rate; properties (lines 19–33 and 36–50) for instance variables grossSales and commissionRate, respectively; method Earnings (lines 53–56) to calculate a CommissionEmployee’s earnings; and method ToString (lines 59–64), which returns the employee’s string representation. The CommissionEmployee’s constructor also passes the first name, last name and social security number to the Employee constructor (line 12) to initialize Employee’s data. Method ToString calls base-class method ToString (line 62) to obtain the Employee-specific information (i.e., first name, last name and social security number).

Example 12.7. CommissionEmployee class that extends Employee.

 1   // Fig. 12.7: CommissionEmployee.cs
 2   // CommissionEmployee class that extends Employee.
 3   using System;
 4
 5   public class CommissionEmployee : Employee
 6   {
 7      private decimal grossSales; // gross weekly sales
 8      private decimal commissionRate; // commission percentage
 9
10      // five-parameter constructor
11      public CommissionEmployee( string first, string last, string ssn,
12         decimal sales, decimal rate ) : base( first, last, ssn )
13      {
14         GrossSales = sales; // validate gross sales via property
15         CommissionRate = rate; // validate commission rate via property
16      } // end five-parameter CommissionEmployee constructor
17
18      // property that gets and sets commission employee's gross sales
19      public decimal GrossSales
20      {
21         get
22         {
23            return grossSales;
24         } // end get
25         set
26         {
27            if ( value >= 0 )
28               grossSales = value;
29            else
30               throw new ArgumentOutOfRangeException(
31                  "GrossSales", value, "GrossSales must be >= 0" );
32         } // end set
33      } // end property GrossSales
34
35      // property that gets and sets commission employee's commission rate
36      public decimal CommissionRate
37      {
38         get
39         {
40            return commissionRate;
41         } // end get
42         set
43         {
44            if ( value > 0 && value < 1 )
45               commissionRate = value;
46            else
47               throw new ArgumentOutOfRangeException( "CommissionRate",
48                  value, "CommissionRate must be > 0 and < 1" );
49         } // end set
50      } // end property CommissionRate
51
52      // calculate earnings; override abstract method Earnings in Employee
53      public override decimal Earnings()    
54      {                                     
55         return CommissionRate * GrossSales;
56      } // end method Earnings              
57
58      // return string representation of CommissionEmployee object
59      public override string ToString()                                    
60      {                                                                    
61         return string.Format( "{0}: {1}
{2}: {3:C}
{4}: {5:F2}",        
62            "commission employee", base.ToString(),                        
63            "gross sales", GrossSales, "commission rate", CommissionRate );
64      } // end method ToString                                             
65   } // end class CommissionEmployee

Creating Indirect Concrete Derived Class BasePlusCommissionEmployee

Class BasePlusCommissionEmployee (Fig. 12.8) extends class CommissionEmployee (line 5) and therefore is an indirect derived class of class Employee. Class BasePlusCommissionEmployee has a constructor (lines 10–15) that takes as arguments a first name, a last name, a social security number, a sales amount, a commission rate and a base salary. It then passes the first name, last name, social security number, sales amount and commission rate to the CommissionEmployee constructor (line 12) to initialize the base class’s data. BasePlusCommissionEmployee also contains property BaseSalary (lines 19–33) to manipulate instance variable baseSalary. Method Earnings (lines 36–39) calculates a BasePlusCommissionEmployee’s earnings. Line 38 in method Earnings calls base class CommissionEmployee’s Earnings method to calculate the commission-based portion of the employee’s earnings. Again, this shows the benefits of code reuse. BasePlusCommissionEmployee’s ToString method (lines 42–46) creates a string representation of a BasePlusCommissionEmployee that contains "base-salaried", followed by the string obtained by invoking base class CommissionEmployee’s ToString method (another example of code reuse), then the base salary. The result is a string beginning with "base-salaried commission employee", followed by the rest of the BasePlusCommissionEmployee’s information. Recall that CommissionEmployee’s ToString method obtains the employee’s first name, last name and social security number by invoking the ToString method of its base class (i.e., Employee)—a further demonstration of code reuse. BasePlusCommissionEmployee’s ToString initiates a chain of method calls that spans all three levels of the Employee hierarchy.

Example 12.8. BasePlusCommissionEmployee class that extends CommissionEmployee.

 1   // Fig. 12.8: BasePlusCommissionEmployee.cs
 2   // BasePlusCommissionEmployee class that extends CommissionEmployee.
 3   using System;
 4
 5   public class BasePlusCommissionEmployee : CommissionEmployee
 6   {
 7      private decimal baseSalary; // base salary per week
 8
 9      // six-parameter constructor
10      public BasePlusCommissionEmployee( string first, string last,
11         string ssn, decimal sales, decimal rate, decimal salary )
12         : base( first, last, ssn, sales, rate )
13      {
14         BaseSalary = salary; // validate base salary via property
15      } // end six-parameter BasePlusCommissionEmployee constructor
16
17      // property that gets and sets
18      // base-salaried commission employee's base salary
19      public decimal BaseSalary
20      {
21         get
22         {
23            return baseSalary;
24         } // end get
25         set
26         {
27            if ( value >= 0 )
28               baseSalary = value;
29            else
30               throw new ArgumentOutOfRangeException( "BaseSalary",
31                  value, "BaseSalary must be >= 0" );
32         } // end set
33      } // end property BaseSalary
34
35      // calculate earnings; override method Earnings in CommissionEmployee
36      public override decimal Earnings()     
37      {                                      
38         return BaseSalary + base.Earnings();
39      } // end method Earnings               
40
41      // return string representation of BasePlusCommissionEmployee object
42      public override string ToString()                                   
43      {                                                                   
44         return string.Format( "base-salaried {0}; base salary: {1:C}",   
45            base.ToString(), BaseSalary );                                
46      } // end method ToString                                            
47   } // end class BasePlusCommissionEmployee

Polymorphic Processing, Operator is and Downcasting

To test our Employee hierarchy, the application in Fig. 12.9 creates an object of each of the four concrete classes SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee. The application manipulates these objects, first via variables of each object’s own type, then polymorphically, using an array of Employee variables. While processing the objects polymorphically, the application increases the base salary of each BasePlusCommissionEmployee by 10% (this, of course, requires determining the object’s type at execution time). Finally, the application polymorphically determines and outputs the type of each object in the Employee array. Lines 10–20 create objects of each of the four concrete Employee derived classes. Lines 24–32 output the string representation and earnings of each of these objects. Each object’s ToString method is called implicitly by WriteLine when the object is output as a string with format items.

Example 12.9. Employee hierarchy test application.

 1   // Fig. 12.9: PayrollSystemTest.cs
 2   // Employee hierarchy test application.
 3   using System;
 4
 5   public class PayrollSystemTest
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // create derived-class objects
10         SalariedEmployee salariedEmployee =                                
11            new SalariedEmployee( "John", "Smith", "111-11-1111", 800.00M );
12         HourlyEmployee hourlyEmployee =                                    
13            new HourlyEmployee( "Karen", "Price",                           
14            "222-22-2222", 16.75M, 40.0M );                                
15         CommissionEmployee commissionEmployee =                            
16            new CommissionEmployee( "Sue", "Jones",                         
17            "333-33-3333", 10000.00M, .06M );                               
18         BasePlusCommissionEmployee basePlusCommissionEmployee =            
19            new BasePlusCommissionEmployee( "Bob", "Lewis",                 
20            "444-44-4444", 5000.00M, .04M, 300.00M );                       
21
22         Console.WriteLine( "Employees processed individually:
" );
23
24         Console.WriteLine( "{0}
earned: {1:C}
",
25            salariedEmployee, salariedEmployee.Earnings() );
26         Console.WriteLine( "{0}
earned: {1:C}
",
27            hourlyEmployee, hourlyEmployee.Earnings() );
28         Console.WriteLine( "{0}
earned: {1:C}
",
29            commissionEmployee, commissionEmployee.Earnings() );
30         Console.WriteLine( "{0}
earned: {1:C}
",
31            basePlusCommissionEmployee,
32            basePlusCommissionEmployee.Earnings() );
33
34         // create four-element Employee array
35         Employee[] employees = new Employee[ 4 ];
36
37         // initialize array with Employees of derived types
38         employees[ 0 ] = salariedEmployee;          
39         employees[ 1 ] = hourlyEmployee;            
40         employees[ 2 ] = commissionEmployee;        
41         employees[ 3 ] = basePlusCommissionEmployee;
42
43         Console.WriteLine( "Employees processed polymorphically:
" );
44
45         // generically process each element in array employees
46         foreach ( Employee currentEmployee in employees )
47         {
48            Console.WriteLine( currentEmployee ); // invokes ToString
49
50            // determine whether element is a BasePlusCommissionEmployee
51            if ( currentEmployee is BasePlusCommissionEmployee )
52            {
53               // downcast Employee reference to
54               // BasePlusCommissionEmployee reference
55               BasePlusCommissionEmployee employee =
56                  ( BasePlusCommissionEmployee ) currentEmployee;
57
58               employee.BaseSalary *= 1.10M ;
59               Console.WriteLine(
60                  "new base salary with 10% increase is: {0:C}",
61                  employee.BaseSalary );
62            } // end if
63
64            Console.WriteLine(
65               "earned {0:C}
", currentEmployee.Earnings() );
66         } // end foreach
67
68         // get type name of each object in employees array
69         for ( int j = 0 ; j < employees.Length; j++ )    
70            Console.WriteLine( "Employee {0} is a {1}", j,
71               employees[ j ].GetType() );                
72      } // end Main
73   } // end class PayrollSystemTest
Employees processed individually:

salaried employee: John Smith
social security number: 111-11-1111
weekly salary: $800.00
earned: $800.00

hourly employee: Karen Price
social security number: 222-22-2222
hourly wage: $16.75; hours worked: 40.00
earned: $670.00

commission employee: Sue Jones
social security number: 333-33-3333
gross sales: $10,000.00
commission rate: 0.06
earned: $600.00

base-salaried commission employee: Bob Lewis
social security number: 444-44-4444
gross sales: $5,000.00
commission rate: 0.04; base salary: $300.00
earned: $500.00

Employees processed polymorphically:

salaried employee: John Smith
social security number: 111-11-1111
weekly salary: $800.00
earned $800.00

hourly employee: Karen Price
social security number: 222-22-2222
hourly wage: $16.75; hours worked: 40.00
earned $670.00
commission employee: Sue Jones
social security number: 333-33-3333
gross sales: $10,000.00
commission rate: 0.06
earned $600.00

base-salaried commission employee: Bob Lewis
social security number: 444-44-4444
gross sales: $5,000.00
commission rate: 0.04; base salary: $300.00
new base salary with 10% increase is: $330.00
earned $530.00

Employee 0 is a SalariedEmployee
Employee 1 is a HourlyEmployee
Employee 2 is a CommissionEmployee
Employee 3 is a BasePlusCommissionEmployee

Assigning Derived-Class Objects to Base-Class References

Line 35 declares employees and assigns it an array of four Employee variables. Lines 38–41 assign a SalariedEmployee object, an HourlyEmployee object, a CommissionEmployee object and a BasePlusCommissionEmployee object to employees[0], employees[1], employees[2] and employees[3], respectively. Each assignment is allowed, because a SalariedEmployee is an Employee, an HourlyEmployee is an Employee, a CommissionEmployee is an Employee and a BasePlusCommissionEmployee is an Employee. Therefore, we can assign the references of SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee objects to base-class Employee variables, even though Employee is an abstract class.

Polymorphically Processing Employees

Lines 46–66 iterate through array employees and invoke methods ToString and Earnings with Employee variable currentEmployee, which is assigned the reference to a different Employee during each iteration. The output illustrates that the appropriate methods for each class are indeed invoked. All calls to virtual methods ToString and Earnings are resolved at execution time, based on the type of the object to which currentEmployee refers. This process is known as dynamic binding or late binding. For example, line 48 implicitly invokes method ToString of the object to which currentEmployee refers. Only the methods of class Employee can be called via an Employee variable—and Employee includes class object’s methods, such as ToString. (Section 11.7 discussed the methods that all classes inherit from class object.) A base-class reference can be used to invoke only methods of the base class.

Giving BasePlusCommissionEmployees 10% Raises

We perform special processing on BasePlusCommissionEmployee objects—as we encounter them, we increase their base salary by 10%. When processing objects polymorphically, we typically do not need to worry about the “specifics,” but to adjust the base salary, we do have to determine the specific type of each Employee object at execution time. Line 51 uses the is operator to determine whether a particular Employee object’s type is BasePlusCommissionEmployee. The condition in line 51 is true if the object referenced by currentEmployee is a BasePlusCommissionEmployee. This would also be true for any object of a BasePlusCommissionEmployee derived class (if there were any), because of the is-a relationship a derived class has with its base class. Lines 55–56 downcast currentEmployee from type Employee to type BasePlusCommissionEmployee—this cast is allowed only if the object has an is-a relationship with BasePlusCommissionEmployee. The condition at line 51 ensures that this is the case. This cast is required if we are to use derived class BasePlusCommissionEmployee’s BaseSalary property on the current Employee object—attempting to invoke a derived-class-only method directly on a base class reference is a compilation error.

Common Programming Error 12.3

Common Programming Error 12.3

Assigning a base-class variable to a derived-class variable (without an explicit downcast) is a compilation error.

Software Engineering Observation 12.4

Software Engineering Observation 12.4

If at execution time the reference to a derived-class object has been assigned to a variable of one of its direct or indirect base classes, it’s acceptable to cast the reference stored in that base-class variable back to a reference of the derived-class type. Before performing such a cast, use the is operator to ensure that the object is indeed an object of an appropriate derived-class type.

When downcasting an object, an InvalidCastException (of namespace System) occurs if at execution time the object does not have an is a relationship with the type specified in the cast operator. An object can be cast only to its own type or to the type of one of its base classes. You can avoid a potential InvalidCastException by using the as operator to perform a downcast rather than a cast operator. For example, in the statement

BasePlusCommissionEmployee employee =
   currentEmployee as BasePlusCommissionEmployee;

employee is assigned a reference to an object that is a BasePlusCommissionEmployee, or the value null if currentEmployee is not a BasePlusCommissionEmployee. You can then compare employee with null to determine whether the cast succeeded.

If the is expression in line 51 is true, the if statement (lines 51–62) performs the special processing required for the BasePlusCommissionEmployee object. Using BasePlusCommissionEmployee variable employee, line 58 accesses the derived-class-only property BaseSalary to retrieve and update the employee’s base salary with the 10% raise.

Lines 64–65 invoke method Earnings on currentEmployee, which calls the appropriate derived-class object’s Earnings method polymorphically. Obtaining the earnings of the SalariedEmployee, HourlyEmployee and CommissionEmployee polymorphically in lines 64–65 produces the same result as obtaining these employees’ earnings individually in lines 24–29. However, the earnings amount obtained for the BasePlusCommissionEmployee in lines 64–65 is higher than that obtained in lines 30–32, due to the 10% increase in its base salary.

Every Object Knows Its Own Type

Lines 69–71 display each employee’s type as a string. Every object in C# knows its own type and can access this information through method GetType, which all classes inherit from class object. Method GetType returns an object of class Type (of namespace System), which contains information about the object’s type, including its class name, the names of its methods, and the name of its base class. Line 71 invokes method GetType on the object to get its runtime class (i.e., a Type object that represents the object’s type). Then method ToString is implicitly invoked on the object returned by GetType. The Type class’s ToString method returns the class name.

Avoiding Compilation Errors with Downcasting

In the previous example, we avoid several compilation errors by downcasting an Employee variable to a BasePlusCommissionEmployee variable in lines 55–56. If we remove the cast operator (BasePlusCommissionEmployee) from line 56 and attempt to assign Employee variable currentEmployee directly to BasePlusCommissionEmployee variable employee, we receive a “Cannot implicitly convert type” compilation error. This error indicates that the attempt to assign the reference of base-class object commissionEmployee to derived-class variable basePlusCommissionEmployee is not allowed without an appropriate cast operator. The compiler prevents this assignment, because a CommissionEmployee is not a BasePlusCommissionEmployee—again, the is-a relationship applies only between the derived class and its base classes, not vice versa.

Similarly, if lines 58 and 61 use base-class variable currentEmployee, rather than derived-class variable employee, to use derived-class-only property BaseSalary, we receive an “'Employee' does not contain a definition for 'BaseSalary'” compilation error on each of these lines. Attempting to invoke derived-class-only methods on a base-class reference is not allowed. While lines 58 and 61 execute only if is in line 51 returns true to indicate that currentEmployee has been assigned a reference to a BasePlusCommissionEmployee object, we cannot attempt to use derived-class BasePlusCommissionEmployee property BaseSalary with base-class Employee reference currentEmployee. The compiler would generate errors in lines 58 and 61, because BaseSalary is not a base-class member and cannot be used with a base-class variable. Although the actual method that’s called depends on the object’s type at execution time, a variable can be used to invoke only those methods that are members of that variable’s type, which the compiler verifies. Using a base-class Employee variable, we can invoke only methods and properties found in class Employee—methods Earnings and ToString, and properties FirstName, LastName and SocialSecurityNumber—and method methods inherited from class object.

Summary of the Allowed Assignments Between Base-Class and Derived-Class Variables

Now that you’ve seen a complete application that processes diverse derived-class objects polymorphically, we summarize what you can and cannot do with base-class and derivedclass objects and variables. Although a derived-class object also is a base-class object, the two are nevertheless different. As discussed previously, derived-class objects can be treated as if they were base-class objects. However, the derived class can have additional derived-class-only members. For this reason, assigning a base-class reference to a derived-class variable is not allowed without an explicit cast—such an assignment would leave the derivedclass members undefined for a base-class object.

We’ve discussed four ways to assign base-class and derived-class references to variables of base-class and derived-class types:

  1. Assigning a base-class reference to a base-class variable is straightforward.

  2. Assigning a derived-class reference to a derived-class variable is straightforward.

  3. Assigning a derived-class reference to a base-class variable is safe, because the derived-class object is an object of its base class. However, this reference can be used to refer only to base-class members. If this code refers to derived-class-only members through the base-class variable, the compiler reports errors.

  4. Attempting to assign a base-class reference to a derived-class variable is a compilation error. To avoid this error, the base-class reference must be cast to a derivedclass type explicitly or must be converted using the as operator. At execution time, if the object to which the reference refers is not a derived-class object, an exception will occur. The is operator can be used to ensure that such a cast is performed only if the object is a derived-class object.

sealed Methods and Classes

Only methods declared virtual, override or abstract can be overridden in derived classes. A method declared sealed in a base class cannot be overridden in a derived class. Methods that are declared private are implicitly sealed, because it’s impossible to override them in a derived class (though the derived class can declare a new method with the same signature as the private method in the base class). Methods that are declared static also are implicitly sealed, because static methods cannot be overridden either. A derived-class method declared both override and sealed can override a base-class method, but cannot be overridden in derived classes further down the inheritance hierarchy.

A sealed method’s declaration can never change, so all derived classes use the same method implementation, and calls to sealed methods are resolved at compile time—this is known as static binding. Since the compiler knows that sealed methods cannot be overridden, it can often optimize code by removing calls to sealed methods and replacing them with the expanded code of their declarations at each method-call location—a technique known as inlining the code.

Performance Tip 12.1

Performance Tip 12.1

The compiler can decide to inline a sealed method call and will do so for small, simple sealed methods. Inlining does not violate encapsulation or information hiding, but does improve performance, because it eliminates the overhead of making a method call.

A class that’s declared sealed cannot be a base class (i.e., a class cannot extend a sealed class). All methods in a sealed class are implicitly sealed. Class string is a sealed class. This class cannot be extended, so applications that use strings can rely on the functionality of string objects as specified in the Framework Class Library.

Common Programming Error 12.4

Common Programming Error 12.4

Attempting to declare a derived class of a sealed class is a compilation error.

Case Study: Creating and Using Interfaces

Our next example (Figs. 12.1112.15) reexamines the payroll system of Section 12.5. Suppose that the company involved wishes to perform several accounting operations in a single accounts-payable application—in addition to calculating the payroll earnings that must be paid to each employee, the company must also calculate the payment due on each of several invoices (i.e., bills for goods purchased). Though applied to unrelated things (i.e., employees and invoices), both operations have to do with calculating some kind of payment amount. For an employee, the payment refers to the employee’s earnings. For an invoice, the payment refers to the total cost of the goods listed on the invoice. Can we calculate such different things as the payments due for employees and invoices polymorphically in a single application? Does C# offer a capability that requires that unrelated classes implement a set of common methods (e.g., a method that calculates a payment amount)? C# interfaces offer exactly this capability.

Interfaces define and standardize the ways in which people and systems can interact with one another. For example, the controls on a radio serve as an interface between a radio’s users and its internal components. The controls allow users to perform a limited set of operations (e.g., changing the station, adjusting the volume, choosing between AM and FM), and different radios may implement the controls in different ways (e.g., using push buttons, dials, voice commands). The interface specifies what operations a radio must permit users to perform but does not specify how they’re performed. Similarly, the interface between a driver and a car with a manual transmission includes the steering wheel, the gear shift, the clutch pedal, the gas pedal and the brake pedal. This same interface is found in nearly all manual-transmission cars, enabling someone who knows how to drive one particular manual-transmission car to drive just about any other. The components of each car may look a bit different, but the general purpose is the same—to allow people to drive the car.

Software objects also communicate via interfaces. A C# interface describes a set of methods and properties that can be called on an object—to tell it, for example, to perform some task or return some piece of information. The next example introduces an interface named IPayable that describes the functionality of any object that must be capable of being paid and thus must offer a method to determine the proper payment amount due. An interface declaration begins with the keyword interface and can contain only abstract methods, properties, indexers and events (events are discussed in Chapter 14, Graphical User Interfaces with Windows Forms: Part 1.) All interface members are implicitly declared both public and abstract. In addition, each interface can extend one or more other interfaces to create a more elaborate interface that other classes can implement.

Common Programming Error 12.5

Common Programming Error 12.5

It’s a compilation error to declare an interface member public or abstract explicitly, because they’re redundant in interface-member declarations. It’s also a compilation error to specify any implementation details, such as concrete method declarations, in an interface.

To use an interface, a class must specify that it implements the interface by listing the interface after the colon (:) in the class declaration. This is the same syntax used to indicate inheritance from a base class. A concrete class implementing the interface must declare each member of the interface with the signature specified in the interface declaration. A class that implements an interface but does not implement all its members is an abstract class—it must be declared abstract and must contain an abstract declaration for each unimplemented member of the interface. Implementing an interface is like signing a contract with the compiler that states, “I will provide an implementation for all the members specified by the interface, or I will declare them abstract.”

Common Programming Error 12.6

Common Programming Error 12.6

Failing to define or declare any member of an interface in a class that implements the interface results in a compilation error.

An interface is typically used when unrelated classes need to share common methods. This allows objects of unrelated classes to be processed polymorphically—objects of classes that implement the same interface can respond to the same method calls. You can create an interface that describes the desired functionality, then implement this interface in any classes requiring that functionality. For example, in the accounts-payable application developed in this section, we implement interface IPayable in any class that must be able to calculate a payment amount (e.g., Employee, Invoice).

An interface often is used in place of an abstract class when there’s no default implementation to inherit—that is, no fields and no default method implementations. Like abstract classes, interfaces are typically public types, so they’re normally declared in files by themselves with the same name as the interface and the .cs file-name extension.

Developing an IPayable Hierarchy

To build an application that can determine payments for employees and invoices alike, we first create an interface named IPayable. Interface IPayable contains method GetPaymentAmount that returns a decimal amount to be paid for an object of any class that implements the interface. Method GetPaymentAmount is a general-purpose version of method Earnings of the Employee hierarchy—method Earnings calculates a payment amount specifically for an Employee, while GetPaymentAmount can be applied to a broad range of unrelated objects. After declaring interface IPayable, we introduce class Invoice, which implements interface IPayable. We then modify class Employee such that it also implements interface IPayable. Finally, we update Employee derived class SalariedEmployee to “fit” into the IPayable hierarchy (i.e., we rename SalariedEmployee method Earnings as GetPaymentAmount).

Good Programming Practice 12.1

Good Programming Practice 12.1

By convention, the name of an interface begins with “I”. This helps distinguish interfaces from classes, improving code readability.

Good Programming Practice 12.2

Good Programming Practice 12.2

When declaring a method in an interface, choose a name that describes the method’s purpose in a general manner, because the method may be implemented by a broad range of unrelated classes.

Classes Invoice and Employee both represent things for which the company must be able to calculate a payment amount. Both classes implement IPayable, so an application can invoke method GetPaymentAmount on Invoice objects and Employee objects alike. This enables the polymorphic processing of Invoices and Employees required for our company’s accounts-payable application.

The UML class diagram in Fig. 12.10 shows the interface and class hierarchy used in our accounts-payable application. The hierarchy begins with interface IPayable. The UML distinguishes an interface from a class by placing the word “interface” in guillemets (« and ») above the interface name. The UML expresses the relationship between a class and an interface through a realization. A class is said to “realize,” or implement, an interface. A class diagram models a realization as a dashed arrow with a hollow arrowhead pointing from the implementing class to the interface. The diagram in Fig. 12.10 indicates that classes Invoice and Employee each realize (i.e., implement) interface IPayable. As in the class diagram of Fig. 12.2, class Employee appears in italics, indicating that it’s an abstract class. Concrete class SalariedEmployee extends Employee and inherits its base class’s realization relationship with interface IPayable.

IPayable interface and class hierarchy UML class diagram.

Figure 12.10. IPayable interface and class hierarchy UML class diagram.

Declaring Interface IPayable

The declaration of interface IPayable begins in Fig. 12.11 at line 3. Interface IPayable contains public abstract method GetPaymentAmount (line 5). The method cannot be explicitly declared public or abstract. Interfaces can have any number of members and interface methods can have parameters.

Example 12.11. IPayable interface declaration.

 1   // Fig. 12.11: IPayable.cs
 2   // IPayable interface declaration.
 3   public interface IPayable
 4   {
 5      decimal GetPaymentAmount(); // calculate payment; no implementation
 6   } // end interface IPayable

Creating Class Invoice

We now create class Invoice (Fig. 12.12) to represent a simple invoice that contains billing information for one kind of part. The class contains properties PartNumber (line 11), PartDescription (line 14), Quantity (lines 27–41) and PricePerItem (lines 44–58) that indicate the part number, the description of the part, the quantity of the part ordered and the price per item. Class Invoice also contains a constructor (lines 17–24) and a ToString method (lines 61–67) that returns a string representation of an Invoice object. The set accessors of properties Quantity and PricePerItem ensure that quantity and pricePerItem are assigned only nonnegative values.

Example 12.12. Invoice class implements IPayable.

 1   // Fig. 12.12: Invoice.cs
 2   // Invoice class implements IPayable.
 3   using System;
 4
 5   public class Invoice : IPayable
 6   {
 7      private int quantity;
 8      private decimal pricePerItem;
 9
10      // property that gets and sets the part number on the invoice
11      public string PartNumber { get; set; }
12
13      // property that gets and sets the part description on the invoice
14      public string PartDescription { get; set; }
15
16      // four-parameter constructor
17      public Invoice( string part, string description, int count,
18         decimal price )
19      {
20         PartNumber = part;
21         PartDescription = description;
22         Quantity = count; // validate quantity via property
23         PricePerItem = price; // validate price per item via property
24      } // end four-parameter Invoice constructor
25
26      // property that gets and sets the quantity on the invoice
27      public int Quantity
28      {
29         get
30         {
31            return quantity;
32         } // end get
33         set
34         {
35            if ( value >= 0 ) // validate quantity
36               quantity = value;
37            else
38               throw new ArgumentOutOfRangeException( "Quantity",
39                  value, "Quantity must be >= 0" );
40         } // end set
41      } // end property Quantity
42
43      // property that gets and sets the price per item
44      public decimal PricePerItem
45      {
46         get
47         {
48            return pricePerItem;
49         } // end get
50         set
51         {
52            if ( value >= 0 ) // validate price
53               quantity = value;
54            else
55               throw new ArgumentOutOfRangeException( "PricePerItem",
56                  value, "PricePerItem must be >= 0" );
57         } // end set
58      } // end property PricePerItem
59
60      // return string representation of Invoice object
61      public override string ToString()
62      {
63         return string.Format(
64            "{0}: 
{1}: {2} ({3}) 
{4}: {5} 
{6}: {7:C}",
65            "invoice", "part number", PartNumber, PartDescription,
66            "quantity", Quantity, "price per item", PricePerItem );
67      } // end method ToString
68
69      // method required to carry out contract with interface IPayable
70      public decimal GetPaymentAmount()                         
71      {                                                         
72         return Quantity * PricePerItem; // calculate total cost
73      } // end method GetPaymentAmount                          
74   } // end class Invoice

Line 5 of Fig. 12.12 indicates that class Invoice implements interface IPayable. Like all classes, class Invoice also implicitly inherits from class object. C# does not allow derived classes to inherit from more than one base class, but it does allow a class to inherit from a base class and implement any number of interfaces. All objects of a class that implement multiple interfaces have the is-a relationship with each implemented interface type. To implement more than one interface, use a comma-separated list of interface names after the colon (:) in the class declaration, as in:

public class ClassName : BaseClassName, FirstInterface, SecondInterface, ...

When a class inherits from a base class and implements one or more interfaces, the class declaration must list the base-class name before any interface names.

Class Invoice implements the one method in interface IPayable—method GetPaymentAmount is declared in lines 70–73. The method calculates the amount required to pay the invoice. The method multiplies the values of quantity and pricePerItem (obtained through the appropriate properties) and returns the result (line 72). This method satisfies the implementation requirement for the method in interface IPayable—we’ve fulfilled the interface contract with the compiler.

Modifying Class Employee to Implement Interface IPayable

We now modify class Employee to implement interface IPayable. Figure 12.13 contains the modified Employee class. This class declaration is identical to that of Fig. 12.4 with two exceptions. First, line 3 of Fig. 12.13 indicates that class Employee now implements interface IPayable. Because of this, we must rename Earnings to GetPaymentAmount throughout the Employee hierarchy. As with method Earnings in the version of class Employee in Fig. 12.4, however, it does not make sense to implement method GetPaymentAmount in class Employee, because we cannot calculate the earnings payment owed to a general Employee—first, we must know the specific type of Employee. In Fig. 12.4, we declared method Earnings as abstract for this reason, and as a result, class Employee had to be declared abstract. This forced each Employee derived class to override Earnings with a concrete implementation.

Example 12.13. Employee abstract base class.

 1   // Fig. 12.13: Employee.cs
 2   // Employee abstract base class.
 3   public abstract class Employee : IPayable
 4   {
 5      // read-only property that gets employee's first name
 6      public string FirstName { get; private set; }
 7
 8      // read-only property that gets employee's last name
 9      public string LastName { get; private set; }
10
11      // read-only property that gets employee's social security number
12      public string SocialSecurityNumber { get; private set; }
13
14      // three-parameter constructor
15      public Employee( string first, string last, string ssn )
16      {
17         FirstName = first;
18         LastName = last;
19         SocialSecurityNumber = ssn;
20      } // end three-parameter Employee constructor
21
22      // return string representation of Employee object
23      public override string ToString()
24      {
25         return string.Format( "{0} {1}
social security number: {2}",
26            FirstName, LastName, SocialSecurityNumber );
27      } // end method ToString
28
29      // Note: We do not implement IPayable method GetPaymentAmount here, so
30      // this class must be declared abstract to avoid a compilation error. 
31      public abstract decimal GetPaymentAmount();                           
32   } // end abstract class Employee

In Fig. 12.13, we handle this situation the same way. Recall that when a class implements an interface, the class makes a contract with the compiler stating that the class either will implement each of the methods in the interface or will declare them abstract. If the latter option is chosen, we must also declare the class abstract. As we discussed in Section 12.4, any concrete derived class of the abstract class must implement the abstract methods of the base class. If the derived class does not do so, it too must be declared abstract. As indicated by the comments in lines 29–30, class Employee of Fig. 12.13 does not implement method GetPaymentAmount, so the class is declared abstract.

Modifying Class SalariedEmployee for Use with IPayable

Figure 12.14 contains a modified version of class SalariedEmployee that extends Employee and implements method GetPaymentAmount. This version of SalariedEmployee is identical to that of Fig. 12.5 with the exception that the version here implements method GetPaymentAmount (lines 35–38) instead of method Earnings. The two methods contain the same functionality but have different names. Recall that the IPayable version of the method has a more general name to be applicable to possibly disparate classes. The remaining Employee derived classes (e.g., HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee) also must be modified to contain method GetPaymentAmount in place of Earnings to reflect the fact that Employee now implements IPayable. We leave these modifications as an exercise and use only SalariedEmployee in our test application in this section.

Example 12.14. SalariedEmployee class that extends Employee.

 1   // Fig. 12.14: SalariedEmployee.cs
 2   // SalariedEmployee class that extends Employee.
 3   using System;
 4
 5   public class SalariedEmployee : Employee
 6   {
 7      private decimal weeklySalary;
 8
 9      // four-parameter constructor
10      public SalariedEmployee( string first, string last, string ssn,
11         decimal salary ) : base( first, last, ssn )
12      {
13         WeeklySalary = salary; // validate salary via property
14      } // end four-parameter SalariedEmployee constructor
15
16      // property that gets and sets salaried employee's salary
17      public decimal WeeklySalary
18      {
19         get
20         {
21            return weeklySalary;
22         } // end get
23         set
24         {
25            if ( value >= 0 ) // validation
26               weeklySalary = value;
27            else
28               throw new ArgumentOutOfRangeException( "WeeklySalary",
29                  value, "WeeklySalary must be >= 0" );
30         } // end set
31      } // end property WeeklySalary
32
33      // calculate earnings; implement interface IPayable method
34      // that was abstract in base class Employee
35      public override decimal GetPaymentAmount()
36      {                                         
37         return WeeklySalary;                   
38      } // end method GetPaymentAmount          
39
40      // return string representation of SalariedEmployee object
41      public override string ToString()
42      {
43         return string.Format( "salaried employee: {0}
{1}: {2:C}",
44            base.ToString(), "weekly salary", WeeklySalary );
45      } // end method ToString
46   } // end class SalariedEmployee

When a class implements an interface, the same is-a relationship provided by inheritance applies. Class Employee implements IPayable, so we can say that an Employee is an IPayable, as are any classes that extend Employee. As such, SalariedEmployee objects are IPayable objects. An object of a class that implements an interface may be thought of as an object of the interface type. Objects of any classes derived from the class that implements the interface can also be thought of as objects of the interface type. Thus, just as we can assign the reference of a SalariedEmployee object to a base-class Employee variable, we can assign the reference of a SalariedEmployee object to an interface IPayable variable. Invoice implements IPayable, so an Invoice object also is an IPayable object, and we can assign the reference of an Invoice object to an IPayable variable.

Software Engineering Observation 12.5

Software Engineering Observation 12.5

Inheritance and interfaces are similar in their implementation of the is-a relationship. An object of a class that implements an interface may be thought of as an object of that interface type. An object of any derived classes of a class that implements an interface also can be thought of as an object of the interface type.

Software Engineering Observation 12.6

Software Engineering Observation 12.6

The is-a relationship that exists between base classes and derived classes, and between interfaces and the classes that implement them, holds when passing an object to a method. When a method parameter receives an argument of a base class or interface type, the method polymorphically processes the object received as an argument.

Using Interface IPayable to Process Invoices and Employees Polymorphically

PayableInterfaceTest (Fig. 12.15) illustrates that interface IPayable can be used to process a set of Invoices and Employees polymorphically in a single application. Line 10 declares payableObjects and assigns it an array of four IPayable variables. Lines 13–14 assign the references of Invoice objects to the first two elements of payableObjects. Lines 15–18 assign the references of SalariedEmployee objects to the remaining two elements of payableObjects. These assignments are allowed because an Invoice is an IPayable, a SalariedEmployee is an Employee and an Employee is an IPayable. Lines 24–29 use a foreach statement to process each IPayable object in payableObjects polymorphically, printing the object as a string, along with the payment due. Lines 27–28 implicitly invokes method ToString off an IPayable interface reference, even though ToString is not declared in interface IPayable—all references (including those of interface types) refer to objects that extend object and therefore have a ToString method. Line 28 invokes IPayable method GetPaymentAmount to obtain the payment amount for each object in payableObjects, regardless of the actual type of the object. The output reveals that the method calls in lines 27–28 invoke the appropriate class’s implementation of methods ToString and GetPaymentAmount. For instance, when currentPayable refers to an Invoice during the first iteration of the foreach loop, class Invoice’s ToString and GetPaymentAmount methods execute.

Example 12.15. Tests interface IPayable with disparate classes.

 1   // Fig. 12.15: PayableInterfaceTest.cs
 2   // Tests interface IPayable with disparate classes.
 3   using System;
 4
 5   public class PayableInterfaceTest
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // create four-element IPayable array
10         IPayable[] payableObjects = new IPayable[ 4 ];
11
12         // populate array with objects that implement IPayable
13         payableObjects[ 0 ] = new Invoice( "01234", "seat", 2, 375.00M );
14         payableObjects[ 1 ] = new Invoice( "56789", "tire", 4, 79.95M );
15         payableObjects[ 2 ] = new SalariedEmployee( "John", "Smith",
16            "111-11-1111", 800.00M );
17         payableObjects[ 3 ] = new SalariedEmployee( "Lisa", "Barnes",
18            "888-88-8888", 1200.00M );
19
20         Console.WriteLine(
21            "Invoices and Employees processed polymorphically:
" );
22
23         // generically process each element in array payableObjects
24         foreach ( var currentPayable in payableObjects )
25         {
26            // output currentPayable and its appropriate payment amount
27            Console.WriteLine( "payment due {0}: {1:C}
",
28               currentPayable, currentPayable.GetPaymentAmount() );
29         } // end foreach
30      } // end Main
31   } // end class PayableInterfaceTest

Invoices and Employees processed polymorphically:

invoice:
part number: 01234 (seat)
quantity: 2
price per item: $375.00
payment due: $750.00
invoice:

part number: 56789 (tire)
quantity: 4
price per item: $79.95
payment due: $319.80

salaried employee: John Smith
social security number: 111-11-1111
weekly salary: $800.00
payment due: $800.00

salaried employee: Lisa Barnes
social security number: 888-88-8888
weekly salary: $1,200.00
payment due: $1,200.00

Software Engineering Observation 12.7

Software Engineering Observation 12.7

All methods of class object can be called by using a reference of an interface type—the reference refers to an object, and all objects inherit the methods of class object.

Common Interfaces of the .NET Framework Class Library

In this section, we overview several common interfaces defined in the .NET Framework Class Library. These interfaces are implemented and used in the same manner as those you create (e.g., interface IPayable in Section 12.7.2). The Framework Class Library’s interfaces enable you to extend many important aspects of C# with your own classes. Figure 12.16 overviews several commonly used Framework Class Library interfaces.

Table 12.16. Common interfaces of the .NET Framework Class Library.

Interface

Description

IComparable

As you learned in Chapter 3, C# contains several comparison operators (e.g., <, <=, >, >=, ==, !=) that allow you to compare simple-type values. In Section 12.8 you’ll see that these operators can be defined to compare two objects. Interface IComparable can also be used to allow objects of a class that implements the interface to be compared to one another. The interface contains one method, CompareTo, that compares the object that calls the method to the object passed as an argument to the method. Classes must implement CompareTo to return a value indicating whether the object on which it’s invoked is less than (negative integer return value), equal to (0 return value) or greater than (positive integer return value) the object passed as an argument, using any criteria you specify. For example, if class Employee implements IComparable, its CompareTo method could compare Employee objects by their earnings amounts. Interface IComparable is commonly used for ordering objects in a collection such as an array. We use IComparable in Chapter 22, Generics, and Chapter 23, Collections.

IComponent

Implemented by any class that represents a component, including Graphical User Interface (GUI) controls (such as buttons or labels). Interface IComponent defines the behaviors that components must implement. We discuss IComponent and many GUI controls that implement this interface in Chapter 14, Graphical User Interfaces with Windows Forms: Part 1, and Chapter 15, Graphical User Interfaces with Windows Forms: Part 2.

IDisposable

Implemented by classes that must provide an explicit mechanism for releasing resources. Some resources can be used by only one program at a time. In addition, some resources, such as files on disk, are unmanaged resources that, unlike memory, cannot be released by the garbage collector. Classes that implement interface IDisposable provide a Dispose method that can be called to explicitly release resources. We discuss IDisposable briefly in Chapter 13, Exception Handling: A Deeper Look. You can learn more about this interface at msdn.microsoft.com/en-us/library/system.idisposable.aspx. The MSDN article Implementing a Dispose Method at msdn.microsoft.com/en-us/library/fs2xkftw.aspx discusses the proper implementation of this interface in your classes.

IEnumerator

Used for iterating through the elements of a collection (such as an array) one element at a time. Interface IEnumerator contains method MoveNext to move to the next element in a collection, method Reset to move to the position before the first element and property Current to return the object at the current location. We use IEnumerator in Chapter 23.

Operator Overloading

Object manipulations are accomplished by sending messages (in the form of method calls) to the objects. This method-call notation is cumbersome for certain kinds of classes, especially mathematical classes. For these classes, it would be convenient to use C#’s rich set of built-in operators to specify object manipulations. In this section, we show how to enable these operators to work with class objects—via a process called operator overloading.

C# enables you to overload most operators to make them sensitive to the context in which they’re used. Some operators are overloaded more frequently than others, especially the various arithmetic operators, such as + and -, where operator notation often is more natural. Figures 12.17 and 12.18 provide an example of using operator overloading with a ComplexNumber class. For a list of overloadable operators, see msdn.microsoft.com/en-us/library/8edha89s.aspx.

Example 12.17. Class that overloads operators for adding, subtracting and multiplying complex numbers.

 1   // Fig. 12.17: ComplexNumber.cs
 2   // Class that overloads operators for adding, subtracting
 3   // and multiplying complex numbers.
 4   using System;
 5
 6   public class ComplexNumber
 7   {
 8      // read-only property that gets the real component
 9      public double Real { get; private set; }
10
11      // read-only property that gets the imaginary component
12      public double Imaginary { get; private set; }
13
14      // constructor
15      public ComplexNumber( double a, double b )
16      {
17         Real = a;
18         Imaginary = b;
19      } // end constructor
20
21      // return string representation of ComplexNumber
22      public override string ToString()
23      {
24         return string.Format( "({0} {1} {2}i)",
25            Real, ( Imaginary < 0 ? "-" : "+" ), Math.Abs( Imaginary ) );
26      } // end method ToString
27
28      // overload the addition operator
29      public static ComplexNumber operator+ (      
30         ComplexNumber x, ComplexNumber y )        
31      {                                            
32         return new ComplexNumber( x.Real + y.Real,
33            x.Imaginary + y.Imaginary );           
34      } // end operator +                          
35
36      // overload the subtraction operator
37      public static ComplexNumber operator- (      
38         ComplexNumber x, ComplexNumber y )        
39      {                                            
40         return new ComplexNumber( x.Real - y.Real,
41            x.Imaginary - y.Imaginary );           
42      } // end operator -                          
43
44      // overload the multiplication operator
45      public static ComplexNumber operator* (             
46         ComplexNumber x, ComplexNumber y )               
47      {                                                   
48         return new ComplexNumber(                        
49            x.Real * y.Real - x.Imaginary * y.Imaginary,  
50            x.Real * y.Imaginary + y.Real * x.Imaginary );
51      } // end operator *                                 
52   } // end class ComplexNumber

Example 12.18. Overloading operators for complex numbers.

 1   // Fig. 12.18: OperatorOverloading.cs
 2   // Overloading operators for complex numbers.
 3   using System;
 4
 5   public class ComplexTest
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // declare two variables to store complex numbers
10         // to be entered by user
11         ComplexNumber x, y;
12
13         // prompt the user to enter the first complex number
14         Console.Write( "Enter the real part of complex number x: " );
15         double realPart = Convert.ToDouble( Console.ReadLine() );
16         Console.Write(
17            "Enter the imaginary part of complex number x: " );
18         double imaginaryPart = Convert.ToDouble( Console.ReadLine() );
19         x = new ComplexNumber( realPart, imaginaryPart );
20
21         // prompt the user to enter the second complex number
22         Console.Write( "
Enter the real part of complex number y: " );
23         realPart = Convert.ToDouble( Console.ReadLine() );
24         Console.Write(
25            "Enter the imaginary part of complex number y: " );
26         imaginaryPart = Convert.ToDouble( Console.ReadLine() );
27         y = new ComplexNumber( realPart, imaginaryPart );
28
29         // display the results of calculations with x and y
30         Console.WriteLine();
31         Console.WriteLine( "{0} + {1} = {2}", x, y, x + y );
32         Console.WriteLine( "{0} - {1} = {2}", x, y, x - y );
33         Console.WriteLine( "{0} * {1} = {2}", x, y, x * y );
34      } // end method Main
35   } // end class ComplexTest

Enter the real part of complex number x: 2
Enter the imaginary part of complex number x: 4

Enter the real part of complex number y: 4
Enter the imaginary part of complex number y: -2

(2 + 4i) + (4 - 2i) = (6 + 2i)
(2 + 4i) - (4 - 2i) = (-2 + 6i)
(2 + 4i) * (4 - 2i) = (16 + 12i)

Class ComplexNumber (Fig. 12.17) overloads the plus (+), minus (-) and multiplication (*) operators to enable programs to add, subtract and multiply instances of class ComplexNumber using common mathematical notation. Lines 9 and 12 define properties for the Real and Imaginary components of the complex number.

Lines 29–34 overload the plus operator (+) to perform addition of ComplexNumbers. Keyword operator, followed by an operator symbol, indicates that a method overloads the specified operator. Methods that overload binary operators must take two arguments. The first argument is the left operand, and the second argument is the right operand. Class ComplexNumber’s overloaded plus operator takes two ComplexNumber references as arguments and returns a ComplexNumber that represents the sum of the arguments. This method is marked public and static, which is required for overloaded operators. The body of the method (lines 32–33) performs the addition and returns the result as a new ComplexNumber. Notice that we do not modify the contents of either of the original operands passed as arguments x and y. This matches our intuitive sense of how this operator should behave—adding two numbers does not modify either of the original numbers. Lines 37–51 provide similar overloaded operators for subtracting and multiplying ComplexNumbers.

Software Engineering Observation 12.8

Software Engineering Observation 12.8

Overload operators to perform the same function or similar functions on class objects as the operators perform on objects of simple types. Avoid nonintuitive use of operators.

Software Engineering Observation 12.9

Software Engineering Observation 12.9

At least one parameter of an overloaded operator method must be a reference to an object of the class in which the operator is overloaded. This prevents you from changing how operators work on simple types.

Class ComplexTest (Fig. 12.18) demonstrates the overloaded ComplexNumber operators +, - and *. Lines 14–27 prompt the user to enter two complex numbers, then use this input to create two ComplexNumbers and assign them to variables x and y.

Lines 31–33 add, subtract and multiply x and y with the overloaded operators, then output the results. In line 31, we perform the addition by using the plus operator with ComplexNumber operands x and y. Without operator overloading, the expression x + y wouldn’t make sense—the compiler wouldn’t know how two objects of class ComplexNumber should be added. This expression makes sense here because we’ve defined the plus operator for two ComplexNumbers in lines 29–34 of Fig. 12.17. When the two ComplexNumbers are “added” in line 31 of Fig. 12.18, this invokes the operator+ declaration, passing the left operand as the first argument and the right operand as the second argument. When we use the subtraction and multiplication operators in lines 32–33, their respective overloaded operator declarations are invoked similarly.

Each calculation’s result is a reference to a new ComplexNumber object. When this new object is passed to the Console class’s WriteLine method, its ToString method (Fig. 12.17, lines 22–26) is implicitly invoked. Line 31 of Fig. 12.18 could be rewritten to explicitly invoke the ToString method of the object created by the overloaded plus operator, as in:

Console.WriteLine( "{0} + {1} = {2}", x, y, ( x + y ).ToString() );

Wrap-Up

This chapter introduced polymorphism—the ability to process objects that share the same base class in a class hierarchy as if they were all objects of the base class. The chapter discussed how polymorphism makes systems extensible and maintainable, then demonstrated how to use overridden methods to effect polymorphic behavior. We introduced the notion of an abstract class, which allows you to provide an appropriate base class from which other classes can inherit. You learned that an abstract class can declare abstract methods that each derived class must implement to become a concrete class, and that an application can use variables of an abstract class to invoke derived class implementations of abstract methods polymorphically. You also learned how to determine an object’s type at execution time. We showed how to create sealed methods and classes. The chapter discussed declaring and implementing an interface as another way to achieve polymorphic behavior, often among objects of different classes. Finally, you learned how to define the behavior of the built-in operators on objects of your own classes with operator overloading.

You should now be familiar with classes, objects, encapsulation, inheritance, interfaces and polymorphism—the most essential aspects of object-oriented programming. Next, we take a deeper look at using exception handling to deal with runtime errors.

Summary

Section 12.1 Introduction

  • With polymorphism, we can design and implement systems that are easily extensible—new classes can be added with little or no modification to the general portions of the application.

Section 12.2 Polymorphism Examples

  • With polymorphism, the same method name and signature can be used to cause different actions to occur, depending on the type of object on which the method is invoked.

  • Polymorphism promotes extensibility: Software that invokes polymorphic behavior is independent of the object types to which messages are sent. New object types that can respond to existing method calls can be incorporated in a system without requiring modification of the base system.

Section 12.3 Demonstrating Polymorphic Behavior

  • Invoking a method on a derived-class object via a base-class reference invokes the derived-class functionality—the type of the referenced object determines which method is called.

  • A base-class reference can be used to invoke only the methods declared in the base class. If an application needs to perform a derived-class-specific operation on a derived-class object referenced by a base-class variable, the application must first downcast the base-class reference to a derived-class reference.

Section 12.4 Abstract Classes and Methods

  • Abstract base classes are incomplete classes for which you never intend to instantiate objects.

  • The purpose of an abstract class is primarily to provide an appropriate base class from which other classes can inherit, and thus share a common design.

  • Classes that can be used to instantiate objects are called concrete classes.

  • You make a class abstract by declaring it with keyword abstract.

  • Each concrete derived class of an abstract base class must provide concrete implementations of the base class’s abstract methods and properties.

  • Failure to implement a base class’s abstract methods and properties in a derived class is a compilation error unless the derived class is also declared abstract.

  • Although we cannot instantiate objects of abstract base classes, we can use them to declare variables that can hold references to objects of any concrete class derived from those abstract classes.

Section 12.5 Case Study: Payroll System Using Polymorphism

  • By declaring a method abstract, we indicate that each concrete derived class must provide an appropriate implementation.

  • All virtual method calls are resolved at execution time, based on the type of the object to which the reference-type variable refers. This process is known as dynamic binding or late binding.

  • The is operator determines whether the type of the object in the left operand matches the type specified by the right operand and returns true if the two have an is-a relationship.

  • The as operator performs a downcast that returns a reference to the appropriate object if the downcast is successful and returns null if the downcast fails.

  • Every object in C# knows its own type and can access this information through method GetType, which all classes inherit from class object.

  • Assigning a base-class reference to a derived-class variable is not allowed without an explicit cast or without using the as operator. The is operator can be used to ensure that such a cast is performed only if the object is a derived-class object.

Section 12.6 sealed Methods and Classes

  • A method that’s declared sealed in a base class cannot be overridden in a derived class.

  • A class that’s declared sealed cannot be a base class (i.e., a class cannot extend a sealed class). All methods in a sealed class are implicitly sealed.

Section 12.7 Case Study: Creating and Using Interfaces

  • Interfaces define and standardize the ways in which things such as people and systems can interact with one another.

  • An interface declaration begins with keyword interface and can contain only abstract methods, properties, indexers and events.

  • All interface members are implicitly declared both public and abstract. They do not specify any implementation details, such as concrete method declarations.

  • Each interface can extend one or more other interfaces to create a more elaborate interface that other classes can implement.

  • To use an interface, a class must specify that it implements the interface by listing it after the colon (:) in the class declaration.

  • A class that implements an interface but doesn’t implement all the interface’s members must be declared abstract and contain an abstract declaration of each unimplemented interface member.

  • The UML expresses the relationship between a class and an interface through a realization. A class is said to “realize,” or implement, an interface.

  • To implement more than one interface, use a comma-separated list of interface names after the colon (:) in the class declaration.

  • Inheritance and interfaces are similar in their implementation of the is-a relationship. An object of a class that implements an interface may be thought of as an object of that interface type.

  • All methods of class object can be called by using a reference of an interface type—the reference refers to an object, and all objects inherit the methods of class object.

Section 12.8 Operator Overloading

  • Method-call notation is cumbersome for certain kinds of classes, especially mathematical classes. Sometimes, it’s convenient to use C#’s built-in operators to specify object manipulations.

  • Keyword operator, followed by an operator, indicates that a method overloads the specified operator. Methods that overload binary operators must be declared static and must take two arguments. The first argument is the left operand, and the second is the right operand.

  • Overload operators to perform the same function or similar functions on class objects as the operators perform on objects of simple types. Avoid nonintuitive use of operators.

Self-Review Exercises

12.1

Fill in the blanks in each of the following statements:

  1. If a class contains at least one abstract method, it must be declared as a(n) class.

  2. Classes from which objects can be instantiated are called classes.

  3. ________ involves using a base-class variable to invoke methods on base-class and derived-class objects, enabling you to “program in the general.”

  4. Methods in a class that do not provide implementations must be declared using keyword ________.

  5. Casting a reference stored in a base-class variable to a derived-class type is called _______.

12.1

  1. abstract.

  2. concrete.

  3. Polymorphism.

  4. abstract.

  5. downcasting.

12.2

State whether each of the statements that follows is true or false. If false, explain why.

  1. It’s possible to treat base-class objects and derived-class objects similarly.

  2. All methods in an abstract class must be declared as abstract methods.

  3. Attempting to invoke a derived-class-only method through a base-class variable is an error.

  4. If a base class declares an abstract method, a derived class must implement that method.

  5. An object of a class that implements an interface may be thought of as an object of that interface type.

12.2

  1. True.

  2. False. An abstract class can include methods with implementations and abstract methods.

  3. True.

  4. False. Only a concrete derived class must implement the method.

  5. True.

Answers to Self-Review Exercises

Exercises

12.3

(Programming in the General) How does polymorphism enable you to program “in the general” rather than “in the specific”? Discuss the key advantages of programming “in the general.”

12.4

(Inheriting Interface vs. Inheriting Implementation) A derived class can inherit “interface” or “implementation” from a base class. How do inheritance hierarchies designed for inheriting interface differ from those designed for inheriting implementation?

12.5

(Abstract Methods) What are abstract methods? Describe the circumstances in which an abstract method would be appropriate.

12.6

(Polymorphism and Extensibility) How does polymorphism promote extensibility?

12.7

(Assigning Base Class and Derived Class References) Discuss four ways in which you can assign base-class and derived-class references to variables of base-class and derived-class types.

12.8

(Abstract Classes vs. Interfaces) Compare and contrast abstract classes and interfaces. Why would you use an abstract class? Why would you use an interface?

12.9

(Payroll System Modification) Modify the payroll system of Figs. 12.412.9 to include private instance variable birthDate in class Employee. Use class Date of Fig. 10.7 to represent an employee’s birthday. Assume that payroll is processed once per month. Create an array of Employee variables to store references to the various employee objects. In a loop, calculate the payroll for each Employee (polymorphically), and add a $100.00 bonus to the person’s payroll amount if the current month is the month in which the Employee’s birthday occurs.

12.10

(Shape Hierarchy) Implement the Shape hierarchy of Fig. 11.3. Omit the Triangle and Tetrahedron classes. Each TwoDimensionalShape should contain read-only abstract property Area to calculate the area of the two-dimensional shape. Each ThreeDimensionalShape should have readonly abstract properties Area and Volume to calculate the surface area and volume, respectively, of the three-dimensional shape. Create an application that uses an array of Shape references to objects of each concrete class in the hierarchy. Print a text description of the object to which each array element refers. Also, in the loop that processes all the shapes in the array, determine whether each shape is a TwoDimensionalShape or a ThreeDimensionalShape. If a shape is a TwoDimensionalShape, display its area. If a shape is a ThreeDimensionalShape, display its area and volume.

12.11

(Payroll System Modification) Modify the payroll system of Figs. 12.412.9 to include an additional Employee derived class, PieceWorker, that represents an employee whose pay is based on the number of pieces of merchandise produced. Class PieceWorker should contain private instance variables wage (to store the employee’s wage per piece) and pieces (to store the number of pieces produced). Provide a concrete implementation of method Earnings in class PieceWorker that calculates the employee’s earnings by multiplying the number of pieces produced by the wage per piece. Create an array of Employee variables to store references to objects of each concrete class in the new Employee hierarchy. Display each Employee’s string representation and earnings.

12.12

(Accounts Payable System Modification) In this exercise, we modify the accounts payable application of Figs. 12.1112.15 to include the complete functionality of the payroll application of Figs. 12.412.9. The application should still process two Invoice objects, but now should process one object of each of the four Employee derived classes. If the object currently being processed is a BasePlusCommissionEmployee, the application should increase the BasePlusCommissionEmployee’s base salary by 10%. Finally, the application should output the payment amount for each object. Complete the following steps to create the new application:

  1. Modify classes HourlyEmployee (Fig. 12.6) and CommissionEmployee (Fig. 12.7) to place them in the IPayable hierarchy as derived classes of the version of Employee (Fig. 12.13) that implements IPayable. [Hint: Change the name of method Earnings to GetPaymentAmount in each derived class.]

  2. Modify class BasePlusCommissionEmployee (Fig. 12.8) such that it extends the version of class CommissionEmployee created in Part a.

  3. Modify PayableInterfaceTest (Fig. 12.15) to polymorphically process two Invoices, one SalariedEmployee, one HourlyEmployee, one CommissionEmployee and one BasePlusCommissionEmployee. First, output a string representation of each IPayable object. Next, if an object is a BasePlusCommissionEmployee, increase its base salary by 10%. Finally, output the payment amount for each IPayable object.

12.13

(Package Inheritance Hierarchy) Use the Package inheritance hierarchy created in Exercise 11.8 to create an application that displays the address information and calculates the shipping costs for several Packages. The application should contain an array of Package objects of classes TwoDayPackage and OvernightPackage. Loop through the array to process the Packages polymorphically. For each Package, use properties to obtain the address information of the sender and the recipient, then print the two addresses as they would appear on mailing labels. Also, call each Package’s CalculateCost method and print the result. Keep track of the total shipping cost for all Packages in the array, and display this total when the loop terminates.

12.14

(Polymorphic Banking Program Using Account Hierarchy) Develop a polymorphic banking application using the Account hierarchy created in Exercise 11.9. Create an array of Account references to SavingsAccount and CheckingAccount objects. For each Account in the array, allow the user to specify an amount of money to withdraw from the Account using method Debit and an amount of money to deposit into the Account using method Credit. As you process each Account, determine its type. If an Account is a SavingsAccount, calculate the amount of interest owed to the Account using method CalculateInterest, then add the interest to the account balance using method Credit. After processing an Account, print the updated account balance obtained by using base-class property Balance.

Making a Difference Exercise

12.15

(CarbonFootprint Interface: Polymorphism) Using interfaces, as you learned in this chapter, you can specify similar behaviors for possibly disparate classes. Governments and companies worldwide are becoming increasingly concerned with carbon footprints (annual releases of carbon dioxide into the atmosphere) from buildings burning various types of fuels for heat, vehicles burning fuels for power, and the like. Many scientists blame these greenhouse gases for the phenomenon called global warming. Create three small classes unrelated by inheritance—classes Building, Car and Bicycle. Write an interface ICarbonFootprint with a GetCarbonFootprint method. Have each of your classes implement that interface, so that its GetCarbonFootprint method calculates an appropriate carbon footprint for that class (check out a few websites that explain how to calculate carbon footprints). Write an application that creates objects of each of the three classes, places references to those objects in List<CarbonFootprint>, then iterates through the List, polymorphically invoking each object’s GetCarbonFootprint method.

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

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