Our next example (Figs. 12.11–12.14) reexamines the payroll system of Section 12.5. Suppose that the company involved wishes to perform several accounting operations in a single accounts-payable app—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 app? Is there a capability that can require unrelated classes to implement a set of common methods (e.g., a method that calculates a payment amount)? 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,
abstract properties,
abstract indexers (not covered in this book) and
abstract 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.
It’s a compilation error to explicitly declare an interface member public
or abstract
, because they’re redundant in interface-member declarations. It’s also a compilation error to specify in an interface any implementation details, such as concrete method declarations.
A class must specify that it implements the interface by listing the interface name 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
.”
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 app developed in this section, we implement interface IPayable
in each class (e.g., Employee
, Invoice
) that must be able to calculate a payment amount.
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
filename extension.
To build an app that can determine payments for employees and invoices alike, we first create an interface named IPayable
. Interface IPayable
contains method GetPayment-Amount
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
.
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 app can invoke method GetPaymentAmount
on Invoice
objects and Employee
objects alike. This enables the polymorphic processing of Invoice
s and Employee
s required for our company’s accounts-payable app.
By convention, the name of an interface begins with I
(e.g., IPayable
). This helps distinguish interfaces from classes, improving code readability.
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.
The UML class diagram in Fig. 12.10 shows the interface and class hierarchy used in our accounts-payable app. 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
. Figure 12.10 could include Section 12.5’s entire Employee
class hierarchy—to keep the forthcoming example small, we did not include classes HourlyEmployee
, CommissionEmployee
and BasePlusCommissionEmployee
.
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.
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 7), PartDescription
(line 8), Quantity
(lines 23–39) and PricePerItem
(lines 42–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 13–20) and a ToString
method (lines 61–63) that returns a string representation of an Invoice
object. The set
accessors of properties Quantity
and PricePerItem
ensure that quantity
and pricePer-Item
are assigned only nonnegative values.
Line 5 indicates that class Invoice
implements interface IPayable
. Like all classes, class Invoice
also implicitly inherits from class object
. All objects of a class can implement multiple interfaces, in which case they 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.
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.
Class Invoice
implements the one method in interface IPayable
—method GetPaymentAmount
is declared in line 66. 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. This method satisfies the implementation requirement for the method in interface IPayable
—we’ve fulfilled the interface contract with the compiler.
We now modify class Employee
to implement interface IPayable
(Fig. 12.13). This class declaration is identical to that of Fig. 12.4 with two exceptions:
Line 3 of Fig. 12.13 indicates that class Employee
now implements interface IPayable
.
Line 27 implements interface IPayable
’s GetPaymentAmount
method.
Notice that GetPaymentAmount
simply calls Employee
’s abstract
method Earnings
. At execution time, when GetPaymentAmount
is called on an object of an Employee
derived class, GetPaymentAmount
calls that class’s concrete Earnings
method, which knows how to calculate earnings for objects of that derived-class type.
When a class implements an interface, the same is-a relationship as inheritance applies. Class Employee
implements IPayable
, so we can say that an Employee
is an IPayable
, and thus any object of an Employee
derived class also is an IPayable
. So, if we update the class hierarchy in Section 12.5 with the new Employee
class in Fig. 12.13, then SalariedEmployee
s, HourlyEmployee
s, CommissionEmployee
s and BasePlusCommissionEmployee
s are all IPayable
objects. Just as we can assign the reference of a SalariedEmployee
derived-class object to a base-class Employee
variable, we can assign the reference of a SalariedEmployee
object (or any other Employee
derived-class object) to an 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.
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.
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.
PayableInterfaceTest
(Fig. 12.14) illustrates that interface IPayable
can be used to process a set of Invoice
s and Employee
s polymorphically in a single app. Lines 12–16 create a List<IPayable>
named payableObjects
and initialize it with four new objects—two Invoice
objects (lines 13–14) and two SalariedEmployee
objects (lines 15–16). These assignments are allowed because an Invoice
is an IPayable
, a SalariedEmployee
is an Employee
and an Employee
is an IPayable
. Lines 22–28 use a foreach
statement to process each IPayable
object in payableObjects
polymorphically, displaying the object as a string
, along with the payment due. Line 25 implicitly invokes method ToString
using the IPayable
interface reference payable
, even though ToString
is not declared in interface IPayable
—all references (including those of interface types) refer to objects of classes that extend object
and therefore have a ToString
method. Line 27 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 25 and 27 invoke the appropriate class’s implementation of methods ToString
and GetPaymentAmount
.
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
.
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). Implementing these interfaces enables you to incorporate objects of your own types into many important aspects of the Framework Class Library. Figure 12.15 overviews several commonly used Framework Class Library interfaces and why you might implement them in your own types.
Interface | Description |
---|---|
IComparable | C# contains several comparison operators (e.g., < , <= , > , >= , == , != ) that allow you to compare simple-type values. Section 10.13 showed that you can overload these operators for your own types. Interface IComparable can be used to allow objects of a class that implements the interface to be compared to one another. The interface contains one method, CompareTo , which compares the object that calls the method to the object passed as an argument. 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 20, Generics, and Chapter 21, Generic Collections; Functional Programming with LINQ/PLINQ. |
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 that are explicitly associated with an object. We discuss IDisposable briefly in Chapter 13, Exception Handling: A Deeper Look. You can learn more about this interface at http://msdn.microsoft.com/library/ system.idisposable . The MSDN article Implementing a Dispose Method at http://msdn.microsoft.com/library/fs2xkftw discusses the proper implementation of this interface in your classes. |
IEnumerator |
Used for iterating through the elements of a collection (such as an array or a List ) one element at a time—the foreach statement uses an IEnumerator object to iterate through elements. 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 21. All IEnumberable objects (Chapter 9) provide a GetEnumerator method that returns an IEnumerator object. |