In this section, we use an inheritance hierarchy containing types of employees in a company’s payroll app to discuss the relationship between a base class and a derived class. In this company:
commission employees—who will be represented as objects of a base class—are paid a percentage of their sales, while
base-salaried commission employees—who will be represented as objects of a derived class—receive a base salary plus a percentage of their sales.
We divide our discussion of the relationship between these employee types into an evolutionary set of five examples that was carefully designed to teach key capabilities for good software engineering with inheritance:
The first example creates class CommissionEmployee
, which directly inherits from class object
. The class declares public
auto-implemented properties for the first name, last name and social security number, and private
instance variables for the commission rate and gross (i.e., total) sales amount.
The second example declares class BasePlusCommissionEmployee
, which also directly inherits from object
. The class declares public
auto-implemented properties for the first name, last name and social security number, and private
instance variables for the commission rate, gross sales amount and base salary. We create the class by writing every line of code the class requires—we’ll soon see that it’s more efficient to create this class by inheriting from CommissionEmployee
.
The third example declares a separate BasePlusCommissionEmployee
class that extends class CommissionEmployee
—that is, a BasePlusCommissionEmployee
is a CommissionEmployee
who also has a base salary. BasePlusCommissionEmployee
attempts to access class CommissionEmployee
’s private
members, but this results in compilation errors because a derived class cannot access its base class’s private
instance variables.
The fourth example shows that if base class CommissionEmployee
’s instance variables are declared as protected
, a BasePlusCommissionEmployee
class that inherits from CommissionEmployee
can access that data directly.
The fifth and final example demonstrates best practice by setting the Commission-Employee
instance variables back to private
in class CommissionEmployee
to enforce good software engineering. Then we show how a separate BasePlusCommissionEmployee
class that inherits from class CommissionEmployee
can use
CommissionEmployee
’s public
methods and properties to manipulate CommissionEmployee
’s private
instance variables in a controlled manner.
In the first four examples, we’ll directly access instance variables in several cases where properties should be used. In the fifth example, we’ll apply effective software engineering techniques that we’ve presented up to this point in the book to create classes that are easy to maintain, modify and debug.
CommissionEmployee
ClassWe begin with class CommissionEmployee
(Fig. 11.4). The colon (:
) followed by class name object
in line 5 indicates that class CommissionEmployee
extends (i.e., inherits from) class object
(an alias for class Object
in namespace System
). You use inheritance to create classes from existing classes. Every class (except object
) extends an existing class. Because class CommissionEmployee
extends object
, class CommissionEmployee
inherits object
’s methods—object
has no fields. (Section 11.7 summarizes object
’s methods.) Every C# class directly or indirectly inherits object
’s methods. If a class does not specify that it inherits from another class, the new class implicitly inherits from object
. For this reason, you typically do not include “:
object
” in your code—we do so in this example, then not again in the book.
CommissionEmployee
Class OverviewCommissionEmployee
’s attributes include public
, getter-only, auto-implemented properties FirstName
, LastName
and SocialSecurityNumber
, and private
instance variables grossSales
and commissionRate
. The class provides
a constructor (lines 14–24)
public
properties (lines 27–62) to set and get grossSales
and commissionRate
, and
expression-bodied methods Earnings
(line 65) and ToString
(lines 68–72).
Because instance variables grossSales
and commissionRate
are private
, other classes cannot directly access these variables. Declaring instance variables as private
and providing public
properties to manipulate and validate them helps enforce good software engineering. The set
accessors of properties GrossSales
and CommissionRate
validate their arguments before assigning the values to instance variables grossSales
and commissionRate
, respectively.
CommissionEmployee
ConstructorConstructors are not inherited, so class CommissionEmployee
does not inherit class object
’s constructor. However, class CommissionEmployee
’s constructor calls object
’s constructor implicitly. In fact, before executing the code in its own body, every derived class’s constructor calls a constructor in its direct base class, either explicitly or implicitly (if no constructor call is specified), to ensure that the instance variables inherited from the base class are initialized properly.
Calling a base-class constructor explicitly is discussed in Section 11.4.3. If the code does not explicitly call the base-class constructor, the compiler generates an implicit call to the base class’s default or parameterless constructor, or a constructor with all default arguments. The comment in line 18 indicates where the implicit call to the base class object
’s constructor is made (you do not write the code for this call). Even if a class does not have constructors, the default constructor that the compiler implicitly declares for the class will call the base class’s default or parameterless constructor. Class object
is the only class that does not have a base class.
After the implicit call to object
’s constructor occurs, lines 19–23 in the constructor assign values to the class’s properties. We do not validate the values of arguments first-Name
, lastName
and socialSecurityNumber
. We certainly could validate the first and last names—perhaps by ensuring that they’re of a reasonable length. Similarly, a social security number could be validated to ensure that it contains nine digits, with or without dashes (e.g., 123-45-6789
or 123456789
).
CommissionEmployee
Method Earnings
Method Earnings
(line 65) calculates a CommissionEmployee
’s earnings by multiplying the commissionRate
and the grossSales
, then returns the result. As a best practice, we should use the properties CommissionRate
and GrossSales
to access the instance variables in line 65. We access them directly here and in the next few examples to help motivate then demonstrate the protected
access modifier. In this chapter’s final example, we’ll follow best practice.
CommissionEmployee
Method ToString
Method ToString
(lines 68–72) is special—it’s one of the methods that every class inherits directly or indirectly from class object
. Method ToString
returns a string
representing an object. It can be called explicitly, but it’s called implicitly by an app whenever an object must be converted to a string
representation, such as when displaying an object with Console
’s Write
or WriteLine
methods or inserting an object in a string
-interpolation expression. By default, class object
’s ToString
method returns the object’s fully qualified class name. For object
, ToString
returns
System.Object
because object
is an alias for class Object
in the System
namespace. ToString
is primarily a placeholder that can be (and typically should be) overridden by a derived class to specify an appropriate string
representation of the data in a derived class object.
Method ToString
of class CommissionEmployee
overrides (redefines) class object
’s ToString
method. When invoked, CommissionEmployee
’s ToString
method returns a string
containing information about the CommissionEmployee
. Line 71 uses the format specifier C
(in "{grossSales:C}"
) to format grossSales
as currency and line 72 uses the format specifier F2
(in "{commissionRate:F2}"
) to format the commissionRate
with two digits to the right of the decimal point.
To override a base-class method, a derived class must declare a method with keyword override
and with the same signature (method name, number of parameters and parameter types) and return type as the base-class method—object
’s ToString
method takes no parameters and returns type string
, so CommissionEmployee
declares ToString
with the same parameter list and return type. As you’ll soon see, the base-class method also must be declared virtual
, which is the case for object
method ToString
.
It’s a compilation error to override a method with one that has a different access modifier. Overriding a method with a more restrictive access modifier would break the is-a relationship. If a public
method could be overridden as a protected
or private
method, the derived-class objects would not be able to respond to the same method calls as base-class objects. In particular, once a method is declared in a base class, the method must have the same access modifier for all that class’s direct and indirect derived classes.
CommissionEmployeeTest
Figure 11.5 tests class CommissionEmployee
. Lines 10–11 create a CommissionEmployee
object and invoke its constructor (lines 14–24 of Fig. 11.4) to initialize it. Again, we append the M
suffix to the gross sales amount and the commission rate to indicate these are decimal
literals. Lines 16–22 in Fig. 11.5 use CommissionEmployee
’s properties to retrieve the object’s instance-variable values for output. Line 23 outputs the amount calculated by the Earnings
method. Lines 25–26 invoke the set
accessors of the object’s GrossSales
and CommissionRate
properties to change the values of instance variables grossSales
and commissionRate
. Line 30 outputs the string
representation of the updated CommissionEmployee
. When an object is output by Console
’s WriteLine
method, which displays the object’s string
representation, the ToString
method is invoked implicitly. Line 31 outputs the updated earnings.
BasePlusCommissionEmployee
Class without Using InheritanceWe now discuss the second part of our introduction to inheritance by declaring and testing the completely new and independent class BasePlusCommissionEmployee
(Fig. 11.6), which contains a first name, last name, social security number, gross sales amount, commission rate and base salary—“Base
” in the class name stands for “base salary” not base class.
BasePlusCommissionEmployee
’s attributes include public
, getter-only, auto-implemented properties for the FirstName
, LastName
and SocialSecurityNumber
, and private
instance variables for the grossSales
, commissionRate
and baseSalary
. The class provides
a constructor (lines 16–27)
public
properties (lines 30–84) for manipulating the grossSales
, commission-Rate
and baseSalary
, and
expression-bodied methods Earnings
(lines 87–88) and ToString
(lines 91–96).
Instance variables grossSales
, commissionRate
and baseSalary
are private
, so objects of other classes cannot directly access these variables. The set
accessors of properties Gross-Sales
, CommissionRate
and BaseSalary
validate their arguments before assigning the values to instance variables grossSales
, commissionRate
and baseSalary
, respectively.
BasePlusCommissionEmployee
’s variables, properties and methods encapsulate all the necessary features of a base-salaried commission employee. Note the similarity between this class and class CommissionEmployee
(Fig. 11.4)—in this example, we do not yet exploit that similarity.
Class BasePlusCommissionEmployee
does not specify that it extends object
with the syntax “:
object
” in line 6, so the class implicitly extends object
. Also, like class CommissionEmployee
’s constructor (lines 14–24 of Fig. 11.4), class BasePlusCommissionEmployee
’s constructor invokes class object
’s default constructor implicitly, as noted in the comment in line 20 of Fig. 11.6.
Class BasePlusCommissionEmployee
’s Earnings
method (lines 87–88) computes the earnings of a base-salaried commission employee. Line 88 adds the employee’s base salary to the product of the commission rate and the gross sales, and returns the result.
Class BasePlusCommissionEmployee
overrides object
method ToString
(lines 91–96) to return a string
containing the BasePlusCommissionEmployee
’s information. Once again, we use format specifier C
to format the gross sales and base salary as currency and format specifier F2
to format the commission rate with two digits of precision to the right of the decimal point.
BasePlusCommissionEmployeeTest
Figure 11.7 tests class BasePlusCommissionEmployee
. Lines 10–11 instantiate a BasePlusCommissionEmployee
object and pass "Bob"
, "Lewis"
, "333-33-3333"
, 5000.00M
, .04M
and 300.00M
to the constructor as the first name, last name, social security number, gross sales, commission rate and base salary, respectively. Lines 16–24 use BasePlusCommissionEmployee
’s properties and methods to retrieve the values of the object’s instance variables and calculate the earnings for output. Line 26 invokes the object’s BaseSalary
property to change the base salary. Property BaseSalary
’s set
accessor (Fig. 11.6, lines 68–84) ensures that instance variable baseSalary
is not assigned a negative value, because an employee’s base salary cannot be negative. Lines 30–31 of Fig. 11.7 invoke the object’s To-String
method implicitly and invoke the object’s Earnings
method.
Much of the code for class BasePlusCommissionEmployee
(Fig. 11.6) is similar, or identical, to that of class CommissionEmployee
(Fig. 11.4). For example, in class BasePlusCommissionEmployee
, properties FirstName
, LastName
and SocialSecurityNumber
are identical to those of class CommissionEmployee
. Classes CommissionEmployee
and BasePlusCommissionEmployee
also both contain private
instance variables commissionRate
and grossSales
, as well as identical properties to manipulate these variables. In addition, the BasePlusCommissionEmployee
constructor is almost identical to that of class CommissionEmployee
, except that BasePlusCommissionEmployee
’s constructor also sets the BaseSalary.
The other additions to class BasePlusCommissionEmployee
are private
instance variable baseSalary
and public
property BaseSalary
. Class BasePlusCommissionEmployee
’s Earnings
method is nearly identical to that of class CommissionEmployee
, except that BasePlusCommissionEmployee
’s Earnings
also adds the baseSalary
. Similarly, class BasePlusCommissionEmployee
’s ToString
method is nearly identical to that of class CommissionEmployee
, except that BasePlusCommissionEmployee
’s ToString
also formats the value of instance variable baseSalary
as currency.
We literally copied the code from class CommissionEmployee
and pasted it into class BasePlusCommissionEmployee
, then modified class BasePlusCommissionEmployee
to include a base salary and methods and properties that manipulate the base salary. This “copy-and-paste” approach is error prone and time consuming. Worse yet, it can spread many physical copies of the same code throughout a system, creating a code-maintenance nightmare. Is there a way to “absorb” the members of one class in a way that makes them part of other classes without copying code? In the next several examples we answer this question, using a more elegant approach to building classes—namely, inheritance.
Copying and pasting code from one class to another can spread errors across multiple source-code files. To avoid duplicating code (and possibly errors) in situations where you want one class to “absorb” the members of another class, use inheritance rather than the “copy-and-paste” approach.
With inheritance, the common members of all the classes in the hierarchy are declared in a base class. When changes are required for these common features, you need to make the changes only in the base class—derived classes then inherit the changes. Without inheritance, changes would need to be made to all the source-code files that contain a copy of the code in question.
CommissionEmployee
–BasePlusCommissionEmployee
Inheritance HierarchyNow we declare class BasePlusCommissionEmployee
(Fig. 11.8), which extends (inherits from) class CommissionEmployee
(Fig. 11.4).1 A BasePlusCommissionEmployee
object is a CommissionEmployee
(because inheritance passes on the capabilities of class CommissionEmployee
), but class BasePlusCommissionEmployee
also has instance variable baseSalary
(Fig. 11.8, line 7). The colon (:
) in line 5 of the class declaration indicates inheritance. As a derived class, BasePlusCommissionEmployee
inherits CommissionEmployee
’s members and can access only those that are non-private
. CommissionEmployee
’s constructor is not inherited. Thus, the public
services of BasePlusCommissionEmployee
include its constructor (lines 11–18), public
methods and properties inherited from class CommissionEmployee
, property BaseSalary
(lines 22–38), method Earnings
(lines 41–42) and method ToString
(lines 45–51). We’ll explain the errors in Fig. 11.8 momentarily.
Each derived-class constructor must implicitly or explicitly call its base-class constructor to ensure that the instance variables inherited from the base class are initialized properly. BasePlusCommissionEmployee
’s six-parameter constructor explicitly calls class CommissionEmployee
’s five-parameter constructor to initialize the CommissionEmployee
portion of a BasePlusCommissionEmployee
object—that is, the FirstName
, LastName
, SocialSecurityNumber
, GrossSales
and CommissionRate
.
Lines 14–15 in BasePlusCommissionEmployee
’s constructor invoke CommissionEmployee
’s constructor (declared at lines 14–24 of Fig. 11.4) via a constructor initializer. In Section 10.5, we used a constructor initializer with keyword this
to call an overloaded constructor in the same class. Line 14 of Fig. 11.8 uses a constructor initializer with keyword base
to invoke the base-class constructor, passing arguments to initialize the base class’s corresponding properties that were inherited into the derived-class object. If BasePlusCommissionEmployee
’s constructor did not invoke CommissionEmployee
’s constructor explicitly, C# would attempt to invoke class CommissionEmployee
’s parameterless or default constructor implicitly. CommissionEmployee
does not have such a constructor, so the compiler would issue an error.
A compilation error occurs if a derived-class constructor calls one of its base-class constructors with arguments that do not match the number and types of parameters specified in one of the base-class constructor declarations.
BasePlusCommissionEmployee
Method Earnings
Lines 41–42 of Fig. 11.8 declare method Earnings
using keyword override
to override the CommissionEmployee
’s Earnings
method, as we did with method ToString
in previous examples. Line 41 causes the first compilation error shown in Fig. 11.8, indicating that we cannot override the base class’s Earnings
method because it was not explicitly “marked virtual, abstract, or override.” The virtual
and abstract
keywords indicate that a base-class method can be overridden in derived classes.2 The override
modifier declares that a derived-class method overrides a virtual
or abstract
base-class method. This modifier also implicitly declares the derived-class method virtual
and allows it to be overridden in derived classes further down the inheritance hierarchy. Adding the keyword virtual
to method Earnings
’ declaration in Fig. 11.4, as in
public virtual decimal Earnings()
eliminates the first compilation error in Fig. 11.8.
The compiler generates additional errors for line 42 of Fig. 11.8, because base class CommissionEmployee
’s instance variables commissionRate
and grossSales
are private
—derived class BasePlusCommissionEmployee
’s methods are not allowed to access the base class’s private
members. The compiler also issues errors at lines 49–50 in method ToString
for the same reason. The errors in BasePlusCommissionEmployee
could have been prevented by using the public
properties inherited from class CommissionEmployee
. For example, line 42, 49 and 50 could have invoked the get
accessors of properties CommissionRate
and GrossSales
to access CommissionEmployee
’s private
instance variables commissionRate
and grossSales
, respectively.
CommissionEmployee
–BasePlusCommissionEmployee
Inheritance Hierarchy Using protected
Instance VariablesTo enable class BasePlusCommissionEmployee
to directly access base-class instance variables grossSales
and commissionRate
, we can declare those members as protected
in the base class. As we discussed in Section 11.3, a base class’s protected
members are accessible to all derived classes of that base class. Class CommissionEmployee
in this example is a modification of the version from Fig. 11.4 that declares its instance variables gross-Sales
and commissionRate
as
protected decimal grossSales; // gross weekly sales
protected decimal commissionRate; // commission percentage
rather than private
. We also declare the Earnings
method virtual
so that BasePlusCommissionEmployee
can override the method. The rest of class CommissionEmployee
in this example is identical to Fig. 11.4. The complete source code for class CommissionEmployee
is included in this example’s project.
BasePlusCommissionEmployee
Class BasePlusCommissionEmployee
(Fig. 11.9) in this example extends the version of class CommissionEmployee
with protected
instance variables grossSales
and commissionRate
. Each BasePlusCommissionEmployee
object contains these CommissionEmployee
instance variables, which are inherited into BasePlusCommissionEmployee
as protected
members. As a result, directly accessing instance variables grossSales
and commissionRate
no longer generates compilation errors in methods Earnings
and ToString
. If another class extends BasePlusCommissionEmployee
, the new derived class also inherits grossSales
and commissionRate
as protected
members.
Class BasePlusCommissionEmployee
does not inherit class CommissionEmployee
’s constructor. However, class BasePlusCommissionEmployee
’s constructor (lines 12–19) calls class CommissionEmployee
’s constructor with a constructor initializer. Again, BasePlusCommissionEmployee
’s constructor must explicitly call CommissionEmployee
’s constructor, because CommissionEmployee
does not provide a parameterless constructor that could be invoked implicitly.
The BasePlusCommissionEmployeeTest
class in this example’s project is identical to that of Fig. 11.7 and produces the same output, so we do not show the code here. Although the version of class BasePlusCommissionEmployee
in Fig. 11.6 does not use inheritance and the version in Fig. 11.9 does, both classes provide the same functionality. The source code in Fig. 11.9 (52 lines) is considerably shorter than that in Fig. 11.6 (97 lines), because a large portion of the class’s functionality is now inherited from CommissionEmployee
—there’s now only one copy of the CommissionEmployee
functionality. This makes the code easier to maintain, modify and debug, because the code related to a CommissionEmployee
exists only in that class.
public
vs. protected
DataWe could have declared base class CommissionEmployee
’s instance variables grossSales
and commissionRate
as public
to enable derived class BasePlusCommissionEmployee
to access the base-class instance variables. However, declaring public
instance variables is poor software engineering, because it allows unrestricted access to the instance variables by any of the class’s clients, greatly increasing the chance of errors and inconsistencies. With protected
instance variables, the derived class gets access to the instance variables, but classes that are not derived from the base class cannot access its variables directly.
protected
Instance VariablesIn this example, we declared base-class instance variables as protected
so that derived classes could access them. Inheriting protected
instance variables enables you to directly access the variables in the derived class without invoking the set
or get
accessors of the corresponding property, thus violating encapsulation. In most cases, it’s better to use private
instance variables and access them via properties to encourage proper software engineering. Your code will be easier to maintain, modify and debug.
Using protected
instance variables creates several potential problems. First, since the derived-class object can set an inherited variable’s value directly without using a property’s set
accessor, a derived-class object can assign an invalid value to the variable. For example, if we were to declare CommissionEmployee
’s instance variable grossSales
as protected
, a derived-class object (e.g., BasePlusCommissionEmployee
) could then directly assign a negative value to grossSales
, making it invalid.
The second problem with protected
instance variables is that derived-class methods are more likely to be written to depend on the base class’s data implementation. In practice, derived classes should depend only on the base-class services (i.e., non-private
methods and properties) and not on the base-class data implementation. With protected
instance variables in the base class, we may need to modify all the derived classes of the base class if the base-class implementation changes. For example, if for some reason we were to change the names of instance variables grossSales
and commissionRate
, then we’d have to do so for all occurrences in which a derived class directly references base-class instance variables grossSales
and commissionRate
. In such a case, the software is said to be fragile or brittle, because a small change in the base class can “break” derived-class implementation. You should be able to change the base-class implementation while still providing the same services to the derived classes. Of course, if the base-class services change, we must reimplement our derived classes.
Declaring base-class instance variables private
(as opposed to protected
) enables the base-class implementation of these instance variables to change without affecting derived-class implementations.
CommissionEmployee
–BasePlusCommissionEmployee
Inheritance Hierarchy Using private Instance VariablesWe now reexamine our hierarchy, this time using the best software engineering practices.
CommissionEmployee
Class CommissionEmployee
(Fig. 11.10) once again declares instance variables grossSales
and commissionRate
as private
(lines 10–11). Methods Earnings
(line 65) and To-String
(lines 68–72) no longer directly access these instance variables—rather they use properties GrossSales
and CommissionRate
to access the data. If we decide to change the instance-variable names, the Earnings
and ToString
declarations will not require modification—only the bodies of the properties GrossSales
and CommissionRate
that directly manipulate the instance variables will need to change. These changes occur solely within the base class—no changes to the derived class are needed. Localizing the effects of changes like this is a good software engineering practice. Derived class BasePlusCommissionEmployee
(Fig. 11.11) inherits from CommissionEmployee
’s and can access the private
base-class members via the inherited public
properties.
BasePlusCommissionEmployee
Class BasePlusCommissionEmployee
(Fig. 11.11) has several changes to its method implementations that distinguish it from the version in Fig. 11.9. Methods Earnings
(Fig. 11.11, line 43) and ToString
(lines 46–47) each invoke property BaseSalary
’s get
accessor to obtain the base-salary value, rather than accessing baseSalary
directly. If we decide to rename instance variable baseSalary
, only the body of property BaseSalary
will need to change.
BasePlusCommissionEmployee
Method Earnings
Class BasePlusCommissionEmployee
’s Earnings
method (Fig. 11.11, line 43) overrides class CommissionEmployee
’s Earnings
method (Fig. 11.10, line 65) to calculate the earnings of a BasePlusCommissionEmployee
. The new version obtains the portion of the employee’s earnings based on commission alone by calling CommissionEmployee
’s Earnings
method with the expression base.Earnings()
(Fig. 11.11, line 43), then adds the base salary to this value to calculate the total earnings of the employee. Note the syntax used to invoke an overridden base-class method from a derived class—place the keyword base
and the member access operator (.
) before the base-class method name. This method invocation is a good software engineering practice—by having BasePlusCommissionEmployee
’s Earnings
method invoke CommissionEmployee
’s Earnings
method to calculate part of a BasePlusCommissionEmployee
object’s earnings, we avoid duplicating the code and reduce code-maintenance problems.
When a base-class method is overridden in a derived class, the derived-class version often calls the base-class version to do a portion of the work. Failure to prefix the base-class method name with the keyword base
and the member access (.) operator when referencing the base class’s method from the derived-class version causes the derived-class method to call itself, creating infinite recursion.
BasePlusCommissionEmployee
Method ToString
Similarly, BasePlusCommissionEmployee
’s ToString
method (Fig. 11.11, lines 46–47) overrides CommissionEmployee
’s (Fig. 11.10, lines 68–72) to return a string
representation that’s appropriate for a base-salaried commission employee. The new version creates part of BasePlusCommissionEmployee string
representation (i.e., the string "commission employee"
and the values of CommissionEmployee
’s data) by calling CommissionEmployee
’s ToString
method with the expression base.ToString()
(Fig. 11.11, line 47) and incorporating the result into the string
returned by the derived class’s ToString
method, which includes the base salary.
BasePlusCommissionEmployee
Class BasePlusCommissionEmployeeTest
performs the same manipulations on a BasePlusCommissionEmployee
object as in Figs. 11.7 and produces the same output, so we do not show it here. Although each BasePlusCommissionEmployee
class you’ve seen behaves identically, the version in Fig. 11.11 is the best engineered. By using inheritance and by using properties that hide the data and ensure consistency, we have efficiently and effectively constructed a well-engineered class.