Polymorphism is available not only via inheritance (as discussed in the preceding chapter), but also via interfaces. Unlike abstract classes, interfaces cannot include any implementation. Like abstract classes, however, interfaces define a set of members that callers can rely on being implemented.
By implementing an interface, a type defines its capabilities. The interface implementation relationship is a “can do” relationship: The type can do what the interface requires an implementing type to do. The interface defines the contract between the types that implement the interface and the code that uses the interface. Types that implement interfaces must declare methods with the same signatures as the methods declared by the implemented interfaces. This chapter discusses implementing and using interfaces.
The IFileCompression
interface shown in Listing 7.1 is an example of an interface implementation.
IFileCompression
defines the methods a type must implement to be used in the same manner as other compression-related classes. The power of interfaces is that they grant the ability to callers to switch among implementations without modifying the calling code.
One key characteristic of an interface is that it has no implementation and no data. Method declarations in an interface have a single semicolon in place of curly braces after the header. Fields (data) cannot appear in an interface declaration. When an interface requires the derived class to have certain data, it declares a property rather than a field. Since the property does not contain any implementation as part of the interface declaration, it doesn’t reference a backing field.
The declared members of an interface describe the members that must be accessible on an implementing type. The purpose of nonpublic members is to make those members inaccessible to other code. Therefore, C# does not allow access modifiers on interface members; instead, it automatically defines them as public.
Consider another example (see Listing 7.2): IListable
defines the members a class needs to support in order for the ConsoleListControl
class to display it. As such, any class that implements IListable
can use the ConsoleListControl
to display itself. The IListable
interface requires a read-only property, ColumnValues
.
The results of Listing 7.2 appear in Output 7.1.
First Name Last Name Phone Address
Dick Traci 123-123-1234 123 Main St., Spokane, WA 99037
Andrew Littman 555-123-4567 1417 Palmary St., Dallas, TX 55555
Mary Hartfelt 444-123-4567 1520 Thunder Way, Elizabethton, PA 44444
John Lindherst 222-987-6543 1 Aerial Way Dr., Monteray, NH 88888
Pat Wilson 123-456-7890 565 Irving Dr., Parksdale, FL 22222
Jane Doe 333-345-6789 123 Main St., Aurora, IL 66666
Title Author Year
Celebration of Discipline Richard Foster 1978
Orthodoxy G.K. Chesterton 1908
The Hitchhiker's Guide to the Galaxy Douglas Adams 1979
In Listing 7.2, the ConsoleListControl
can display seemingly unrelated classes (Contact
and Publication
). Any class can be displayed provided that it implements the required interface. As a result, the ConsoleListControl.List()
method relies on polymorphism to appropriately display whichever set of objects it is passed. Each class has its own implementation of ColumnValues
, and converting a class to IListable
still allows the particular class’s implementation to be invoked.
Declaring a class to implement an interface is similar to deriving from a base class: The implemented interfaces appear in a comma-separated list along with the base class. The base class specifier (if there is one) must come first, but otherwise order is not significant. Classes can implement multiple interfaces, but may only derive directly from one base class. An example appears in Listing 7.3.
Once a class declares that it implements an interface, all members of the interface must be implemented. An abstract class is permitted to supply an abstract implementation of an interface member. A nonabstract implementation may throw a NotImplementedException
type exception in the method body, but somehow, an implementation of the member must be supplied.
One important characteristic of interfaces is that they can never be instantiated; you cannot use new
to create an interface, and therefore, interfaces do not have constructors or finalizers. Interface instances are available only by instantiating a type that implements the interface. Furthermore, interfaces cannot include static members. One key interface purpose is polymorphism, and polymorphism without an instance of the implementing type is of little value.
Each interface member behaves like an abstract method, forcing the derived class to implement the member. Therefore, it is not possible to use the abstract modifier on interface members explicitly.
When implementing an interface member in a type there are two ways to do so: explicitly or implicitly. So far we’ve seen only implicit implementations, where the type member that implements the interface member is a public member of the implementing type.
Explicitly implemented methods are available only by calling through the interface itself; this is typically achieved by casting an object to the interface. For example, to call IListable.ColumnValues
in Listing 7.4, you must first cast the contact to IListable
because of ColumnValues
’ explicit implementation.
The cast and the call to ColumnValues
occur within the same statement in this case. Alternatively, you could assign contact2
to an IListable
variable before calling ColumnValues
.
To declare an explicit interface member implementation, prefix the member name with the interface name (see Listing 7.5).
Listing 7.5 implements ColumnValues
explicitly by prefixing the property name with IListable
. Furthermore, since explicit interface implementations are directly associated with the interface, there is no need to modify them with virtual
, override
, or public
, and, in fact, these modifiers are not allowed. The method is not treated as a public member of the class, so marking it as public
would be misleading.
Notice that CompareTo()
in Listing 7.5 does not include the IComparable
prefix; it is implemented implicitly. With implicit member implementation, it is only necessary for the member to be public and for the member’s signature to match the interface member’s signature. Interface member implementation does not require the override
keyword or any indication that this member is tied to the interface. Furthermore, since the member is declared just as any other class member, code that calls implicitly implemented members can do so directly, just as it would any other class member:
result = contact1.CompareTo(contact2);
In other words, implicit member implementation does not require a cast because the member is not hidden from direct invocation on the implementing class.
Many of the modifiers disallowed on an explicit member implementation are required or are optional on an implicit implementation. For example, implicit member implementations must be public
. Furthermore, virtual
is optional depending on whether derived classes may override the implementation. Eliminating virtual
will cause the member to behave as though it is sealed
.
The key difference between implicit and explicit member interface implementation is not in the syntax of the method declaration, but in the ability to access the method by name through an instance of the type rather than via the interface.
When building a class hierarchy, it’s desirable to model real-world “is a” relationships—a giraffe is a mammal, for example. These are “semantic” relationships. Interfaces are often used to model “mechanism” relationships. A PdaItem
“is not a” “comparable,” but it might well be IComparable
. This interface has nothing to do with the semantic model; it’s a detail of the implementation mechanism. Explicit interface implementation is a technique for enabling the separation of mechanism concerns from model concerns. Forcing the caller to convert the object to an interface such as IComparable
before treating the object as “comparable” explicitly separates out in the code when you are talking to the model and when you are dealing with its implementation mechanisms.
In general, it is preferable to limit the public surface area of a class to be “all model” with as little extraneous mechanism as possible. (Unfortunately, some mechanisms are unavoidable in .NET. You cannot get a giraffe’s hash code or convert a giraffe to a string. However, you can get a Giraffe
’s hash code [GetHashCode()
] and convert it to a string
[ToString()
]. By using object
as a common base class, .NET mixes model code with mechanism code even if only to a limited extent.)
Here are several guidelines that will help you choose between an explicit and an implicit implementation.
• Is the member a core part of the class functionality?
Consider the ColumnValues
property implementation on the Contact
class. This member is not an integral part of a Contact
type but a peripheral member probably accessed only by the ConsoleListControl
class. As such, it doesn’t make sense for the member to be immediately visible on a Contact
object, cluttering up what could potentially already be a large list of members.
Alternatively, consider the IFileCompression.Compress()
member. Including an implicit Compress()
implementation on a ZipCompression
class is a perfectly reasonable choice, since Compress()
is a core part of the ZipCompression
class’s behavior, so it should be directly accessible from the ZipCompression
class.
• Is the interface member name appropriate as a class member?
Consider an ITrace
interface with a member called Dump()
that writes out a class’s data to a trace log. Implementing Dump()
implicitly on a Person
or Truck
class would result in confusion as to what operation the method performs. Instead, it is preferable to implement the member explicitly so that only from a data type of ITrace
, where the meaning is clearer, can the Dump()
method be called. Consider using an explicit implementation if a member’s purpose is unclear on the implementing class.
• Is there already a class member with the same signature?
Explicit interface member implementation does not add a named element to the type’s declaration space. Therefore, if there is already a potentially conflicting member of a type, a second one can be provided with the same name or signature as long as it is an explicit interface member.
Much of the decision regarding implicit versus explicit interface member implementation comes down to intuition. However, these questions provide suggestions about what to consider when making your choice. Since changing an implementation from implicit to explicit results in a version-breaking change, it is better to err on the side of defining interfaces explicitly, allowing them to be changed to implicit later on. Furthermore, since the decision between implicit and explicit does not have to be consistent across all interface members, defining some methods as explicit and others as implicit is fully supported.
Just as with a derived type and a base class, a conversion from an implementing type to its implemented interface is an implicit conversion. No cast operator is required because an instance of the implementing type will always provide all the members in the interface, and therefore, the object can always be converted successfully to the interface type.
Although the conversion will always be successful from the implementing type to the implemented interface, many different types could implement a particular interface, so you can never be certain that a “downward” cast from an interface to one of its implementing types will be successful. Therefore, converting from an interface to one of its implementing types requires an explicit cast.
Interfaces can derive from each other, resulting in an interface that inherits all the members in its base interfaces. As shown in Listing 7.6, the interfaces directly derived from IReadableSettingsProvider
are the explicit base interfaces.
In this case, ISettingsProvider
derives from IReadableSettingsProvider
, and therefore inherits its members. If IReadableSettingsProvider
also had an explicit base interface, ISettingsProvider
would inherit those members too, and the full set of interfaces in the derivation hierarchy would simply be the accumulation of base interfaces.
It is interesting to note that if GetSetting()
is implemented explicitly, it must be done using IReadableSettingsProvider
. The declaration with ISettingsProvider
in Listing 7.7 will not compile.
The results of Listing 7.7 appear in Output 7.2.
'ISettingsProvider.GetSetting' in explicit interface declaration
is not a member of interface.
This output appears in addition to an error indicating that IReadableSettingsProvider.GetSetting()
is not implemented. The fully qualified interface member name used for explicit interface member implementation must reference the interface name in which it was originally declared.
Even though a class implements an interface (ISettingsProvider
) which is derived from a base interface (IReadableSettingsProvider
), the class can still declare an implementation of both interfaces overtly, as Listing 7.8 demonstrates.
In this listing, there is no change to the interface’s implementations on the class, and although the additional interface implementation declaration on the class header is superfluous, it can provide better readability.
The decision to provide multiple interfaces rather than just one combined interface depends largely on what the interface designer wants to require of the implementing class. By providing an IReadableSettingsProvider
interface, the designer communicates that implementers are required only to implement a settings provider that retrieves settings. They do not have to be able to write to those settings. This reduces the implementation burden by not imposing the complexities of writing settings as well.
In contrast, implementing ISettingsProvider
assumes that there is never a reason to have a class that can write settings without reading them. The inheritance relationship between ISettingsProvider
and IReadableSettingsProvider
, therefore, forces the combined total of both interfaces on the ISettingsProvider
class.
One final but important note: Although inheritance is the correct term, conceptually it is more accurate to realize that an interface represents a contract; and one contract is allowed to specify that the provisions of another contract must also be followed. So, the code ISettingsProvider : IReadableSettingsProvider
conceptually states that the ISettingsProvider
contract requires also respecting the IReadableSettingsProvider
contract rather than that the ISettingsProvider
“is a kind of” IReadableSettingsProvider
. That being said, the remainder of the chapter will continue using the inheritance relationship terminology in accordance with the standard C# terminology.
Just as classes can implement multiple interfaces, interfaces can inherit from multiple interfaces; the syntax is consistent with class derivation and implementation, as shown in Listing 7.9.
It is unusual to have an interface with no members, but if implementing both interfaces together is predominant, it is a reasonable choice for this case. The difference between Listing 7.9 and Listing 7.6 is that it is now possible to implement IWriteableSettingsProvider
without supplying any read capability. Listing 7.6’s FileSettingsProvider
is unaffected, but if it used explicit member implementation, specifying which interface a member belongs to changes slightly.
Perhaps one of the most important features of extension methods is the fact that they work with interfaces in addition to classes. The syntax is identical to that of extension methods for classes. The extended type (the first parameter and the parameter prefixed with this
) is the interface that we extend. Listing 7.10 shows an extension method for IListable()
. It is declared on Listable
.
Notice that in this example, the extension method is not on for an IListable
parameter (although it could have been), but rather an IListable[]
parameter. This demonstrates that C# allows extension methods not only on an instance of a particular type, but also on a collection of those objects. Support for extension methods is the foundation on which LINQ is implemented. IEnumerable
is the fundamental interface that all collections implement. By defining extension methods for IEnumerable
, LINQ support was added to all collections. This radically changed programming with collections; we will explore this topic in detail in Chapter 14.
As Listing 7.3 demonstrated, a single class can implement any number of interfaces in addition to deriving from a single class. This feature provides a possible workaround for the lack of multiple inheritance support in C# classes. The process uses aggregation as described in the preceding chapter, but you can vary the structure slightly by adding an interface to the mix, as shown in Listing 7.11.
IPerson
ensures that the signatures between the Person
members and the same members duplicated onto Contact
are consistent. The implementation is still not synonymous with multiple inheritance, however, because new members added to Person
will not be added to Contact
.
One possible improvement that works if the implemented members are methods (not properties) is to define interface extension methods for the additional functionality “derived” from the second base class. An extension method on IPerson
could provide a method called VerifyCredentials()
, for example, and all classes that implement IPerson
, even an IPerson
interface that had no members but just extension methods, would have a default implementation of VerifyCredentials()
. What makes this a viable approach is that polymorphism is still available, as is overriding. Overriding is supported because any instance implementation of a method will take priority over an extension method with the equivalent static signature.
When creating a new version of a component or application that other developers have programmed against, you should not change interfaces. Because interfaces define a contract between the implementing class and the class using the interface, changing the interface is changing the contract, which will possibly break any code written against the interface.
Changing or removing a particular interface member signature is obviously a code-breaking change, as any call to that member will no longer compile without modification. The same is true when changing public or protected member signatures on a class. However, unlike with classes, adding members to an interface could also prevent code from compiling without additional changes. The problem is that any class implementing the interface must do so entirely, and implementations for all members must be provided. With new interface members, the compiler will require that developers add new interface members to the class implementing the interface.
The creation of IDistributedSettingsProvider
in Listing 7.12 serves as a good example of extending an interface in a version-compatible way. Imagine that at first, only the ISettingsProvider
interface is defined (as it was in Listing 7.6). In the next version, however, it is determined that per-machine settings are required. To enable this, the IDistributedSettingsProvider
interface is created, and it derives from ISettingsProvider
.
The important factor is that programmers with classes that implement ISettingsProvider
can choose to upgrade the implementation to include IDistributedSettingsProvider
, or they can ignore it.
If instead of creating a new interface, the machine-related methods are added to ISettingsProvider
, classes implementing this interface will no longer successfully compile with the new interface definition, and instead a version-breaking change will occur.
Changing interfaces during the development phase is obviously acceptable, although perhaps laborious if implemented extensively. However, once an interface is released, it should not be changed. Instead, a second interface should be created, possibly deriving from the original interface.
(Listing 7.12 includes XML comments describing the interface members, as discussed further in Chapter 9.)
Interfaces introduce another category of data types. (They are one of the few categories of types that don’t extend System.Object
.2) Unlike classes, however, interfaces can never be instantiated. An interface instance is accessible only via a reference to an object that implements the interface. It is not possible to use the new
operator with an interface; therefore, interfaces cannot contain any constructors or finalizers. Furthermore, static members are not allowed on interfaces.
2. The others being pointer types and type parameter types. However, every interface type is convertible to System.Object
, and it is permissible to call the methods of System.Object
on any instance of an interface, so this is perhaps a hairsplitting distinction.
Interfaces are closer to abstract classes, sharing such features as the lack of instantiation capability. Table 7.1 lists additional comparisons.
Given that abstract classes and interfaces have their own sets of advantages and disadvantages, you must make a cost-benefit decision based on the comparisons in Table 7.1 and the guidelines below in order to make the right choice.
Interfaces with no members at all, inherited or otherwise, are sometimes used to represent information about a type. For example, you might create a marker IObsolete
interface to indicate that a particular type has been replaced by another type. This is generally considered to be an abuse of the interface mechanism; interfaces should be used to represent what functions a type can perform, not to indicate facts about particular types. Instead of marker interfaces, use attributes. See Chapter 17 for more details.
Interfaces are a key element of object-oriented programming in C#. They provide functionality similar to abstract classes but without using up the single-inheritance option, and also support implementation of multiple interfaces.
In C#, the implementation of interfaces can be either explicit or implicit, depending on whether the implementing class is to expose an interface member directly or only via a conversion to the interface. Furthermore, the granularity of whether the implementation is explicit or implicit is at the member level: One member may be implicitly implemented while another member of the same interface is explicitly implemented.
The next chapter looks at value types and discusses the importance of defining custom value types; at the same time, the chapter points out the subtle problems that they can introduce.
3.147.7.154