Case Study: Payroll System Using Polymorphism

Case Study Payroll System Using Polymorphism

This section reexamines the CommissionEmployee-BasePlusCommissionEmployee hierarchy that we explored throughout Section 10.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 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 BasePlusCommissionEmployeewhich extends CommissionEmployeerepresents the last employee type. The UML class diagram in Fig. 11.2 shows the inheritance hierarchy for our polymorphic employee payroll application. Note that abstract class Employee is italicized, as per the convention of the UML.

Figure 11.2. Employee hierarchy UML class diagram.

Abstract base class Employee declares the "interface" to the hierarchythat 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 11.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 private instance variables firstName, lastName and socialSecurityNumber appear in abstract base class Employee.

Software Engineering Observation 11 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 hierarchyeach 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 hierarchya 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.

11.5.1. Creating Abstract Base Class Employee

Class Employee (Fig. 11.4) provides methods Earnings and ToString, in addition to the properties that manipulate Employee's instance variables. 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 methodthere is 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 classbecause 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 first name, last name and social security number of the employee. 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. 11.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 are not overridden in any of the derived classeseach of these properties is inherited and used "as is" by each of the derived classes.]

Figure 11.3. Polymorphic interface for the Employee hierarchy classes.

Let us consider class Employee's declaration (Fig. 11.4). The class includes a constructor that takes the first name, last name and social security number as arguments (lines 1015); read-only properties for obtaining the first name, last name and social security number (lines 1824, 2733 and 3642, respectively); method ToString (lines 4549), which uses properties to return the string representation of Employee; and abstract method Earnings (line 52), which must be implemented by concrete derived classes. Note that the Employee constructor does not validate the social security number in this example. Normally, such validation should be provided.

Why did we declare Earnings as an abstract method? It simply does not make sense to provide an implementation of this method in class Employee. We cannot calculate the earnings for a general Employeewe 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.

11.5.2. Creating Concrete Derived Class SalariedEmployee

Class SalariedEmployee (Fig. 11.5) extends class Employee (line 3) and overrides Earnings (lines 2831), which makes SalariedEmployee a concrete class. The class includes a constructor (lines 812) that takes a first name, a last name, a social security number and a weekly salary as arguments; property WeeklySalary to manipulate instance variable weeklySalary, including a set accessor that ensures we assign only non-negative values to weeklySalary (lines 1525); method Earnings (lines 2831) to calculate a SalariedEmployee's earnings; and method ToString (lines 3438), 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 9) via a constructor initializer to initialize the private instance variables not inherited from the base class. 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 abstractotherwise, a compilation error occurs (and, of course, we want SalariedEmployee to be a concrete class).

Figure 11.5. SalariedEmployee class that extends Employee.

(This item is displayed on page 522 in the print version)

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

Method ToString (lines 3438) of class SalariedEmployee overrides Employee method ToString. If class SalariedEmployee did not override ToString, SalariedEmployee would have inherited the Employee version of ToString. 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 37)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.

11.5.3. Creating Concrete Derived Class HourlyEmployee

Class HourlyEmployee (Fig. 11.6) also extends class Employee (line 3). The class includes a constructor (lines 915) that takes as arguments a first name, a last name, a social security number, an hourly wage and the number of hours worked. Lines 1828 and 3142 declare properties Wage and Hours for instance variables wage and hours, respectively. The set accessor in property Wage (lines 2427) ensures that wage is non-negative, and the set accessor in property Hours (lines 3741) ensures that hours is in the range 0168 (the total number of hours in a week) inclusive. Class HourlyEmployee also includes method Earnings (lines 4551) to calculate an HourlyEmployee's earnings; and method ToString (lines 5459), which returns the employee's type, namely, "hourly employee: ", and employee-specific information. Note that the HourlyEmployee constructor, like the SalariedEmployee constructor, passes the first name, last name and social security number to the base class Employee constructor (line 11) to initialize the base class's private instance variables. Also, method ToString calls base class method ToString (line 58) to obtain the Employee-specific information (i.e., first name, last name and social security number)this is another nice example of code reuse.

Figure 11.6. HourlyEmployee class that extends Employee.

(This item is displayed on pages 523 - 524 in the print version)

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

11.5.4. Creating Concrete Derived Class CommissionEmployee

Class CommissionEmployee (Fig. 11.7) extends class Employee (line 3). The class includes a constructor (lines 914) that takes a first name, a last name, a social security number, a sales amount and a commission rate; properties (lines 1728 and 3141) for instance variables commissionRate and grossSales, respectively; method Earnings (lines 4447) to calculate a CommissionEmployee's earnings; and method ToString (lines 5055), which returns the employee's type, namely, "commission employee: ", and employee-specific information.

Figure 11.7. CommissionEmployee class that extends Employee.

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

The CommissionEmployee's constructor also passes the first name, last name and social security number to the Employee constructor (line 10) to initialize Employee's private instance variables. Method ToString calls base class method ToString (line 53) to obtain the Employee-specific information (i.e., first name, last name and social security number).

11.5.5. Creating Indirect Concrete Derived Class BasePlusCommissionEmployee

Class BasePlusCommissionEmployee (Fig. 11.8) extends class CommissionEmployee (line 3) and therefore is an indirect derived class of class Employee. Class BasePlusCommissionEmployee has a constructor (lines 813) 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 10) to initialize the base class's private data members. BasePlusCommissionEmployee also contains property BaseSalary (lines 1727) to manipulate instance variable baseSalary. Method Earnings (lines 3033) calculates a BasePlusCommissionEmployee's earnings. Note that line 32 in method Earnings calls base class CommissionEmployee's Earnings method to calculate the commission-based portion of the employee's earnings. This is another nice example of code reuse. BasePlusCommissionEmployee's ToString method (lines 3640) 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)yet another example of code reuse. Note that BasePlusCommissionEmployee's ToString initiates a chain of method calls that span all three levels of the Employee hierarchy.

Figure 11.8. BasePlusCommissionEmployee class that extends CommissionEmployee.

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

11.5.6. Polymorphic Processing, Operator is and Downcasting

To test our Employee hierarchy, the application in Fig. 11.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 1020 create objects of each of the four concrete Employee derived classes. Lines 2432 output the string representation and earnings of each of these objects. Note that each object's ToString method is called implicitly by Write when the object is output as a string with format items.

Figure 11.9. Employee hierarchy test application.

(This item is displayed on pages 527 - 529 in the print version)

1 // Fig. 11.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} {1}: {2:C} ", 25 salariedEmployee, "earned", salariedEmployee.Earnings() ); 26 Console.WriteLine( "{0} {1}: {2:C} ", 27 hourlyEmployee, "earned", hourlyEmployee.Earnings() ); 28 Console.WriteLine( "{0} {1}: {2:C} ", 29 commissionEmployee, "earned", commissionEmployee.Earnings() ); 30 Console.WriteLine( "{0} {1}: {2:C} ", 31 basePlusCommissionEmployee, 32 "earned", 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

Line 35 declares employees and assigns it an array of four Employee variables. Lines 3841 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.

Lines 4666 iterate through array employees and invoke methods ToString and Earnings with Employee variable currentEmployee, which is assigned the reference to a different Employee in the array during each iteration. The output illustrates that the appropriate methods for each class are indeed invoked. All calls to method's 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. As a result of dynamic binding, the CLR decides which class's ToString method to call at execution time rather than at compile time. Note that only the methods of class Employee can be called via an Employee variableand Employee, of course, includes the methods of class object, such as ToString. (Section 10.7 discussed the set of methods that all classes inherit from class object.) A base class reference can be used to invoke only methods of the base class.

We perform special processing on BasePlusCommissionEmployee objectsas 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 5556 downcast currentEmployee from type Employee to type BasePlusCommissionEmployeethis 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 objectattempting to invoke a derived-class-only method directly on a base class reference is a compilation error.

Common Programming Error 11 3

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

Software Engineering Observation 11 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 is 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.

Common Programming Error 11 4

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.

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

Lines 6465 invoke method Earnings on currentEmployee, which calls the appropriate derived class object's Earnings method polymorphically. Note that obtaining the earnings of the SalariedEmployee, HourlyEmployee and CommissionEmployee polymorphically in lines 6465 produces the same result as obtaining these employees' earnings individually in lines 2432. However, the earnings amount obtained for the BasePlusCommissionEmployee in lines 6465 is higher than that obtained in lines 3032, due to the 10% increase in its base salary.

Lines 6971 display each employee's type as a string. Every object in C# knows its own class 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 public 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.

In the previous example, we avoid several compilation errors by downcasting an Employee variable to a BasePlusCommissionEmployee variable in lines 5556. 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 BasePlusCommissionEmployeeagain, 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 is 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 Employeemethods Earnings and ToString, and properties FirstName, LastName and SocialSecurityNumber.

11.5.7. Summary of the Allowed Assignments Between Base Class and Derived Class Variables

Now that you have seen a complete application that processes diverse derived class objects polymorphically, we summarize what you can and cannot do with base class and derived class objects and variables. Although a derived class object also is a base class object, the two objects are nevertheless different. As discussed previously, derived class objects can be treated as if they are 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 castsuch an assignment would leave the derived class members undefined for a base class object.

We have 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 derived class type explicitly. At execution time, if the object to which the reference refers is not a derived class object, an exception will occur. (For more on exception handling, see Chapter 12, Exception Handling.) 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

Категории