Polymorphism is available in C# not only via inheritance (as discussed in Chapter 7) but also via interfaces. Unlike abstract classes, interfaces could not include any implementation—until C# 8.0. (But even in C# 8.0, it is questionable whether you should use this capability except for “versioning” the interfaces.) 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. It concludes with default implemented members on interfaces and the host of paradigms (and complexities) that this new feature introduces.
The IFileCompression interface shown in Listing 8.1 is an example of an interface implementation. By convention—a convention so strong it is universal—the interface name is PascalCase with a capital “I” prefix.
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.
Prior to C# 8.0, one of the key characteristics of an interface was that it had no implementation and no data (fields). Method declarations in an interface always had a single semicolon in place of curly braces after the declaration. Properties, while looking like automatically implemented properties, had no backing fields. In fact, fields (data) could not appear in an interface declaration either.
Many of these rules were relaxed in C# 8.0 for the purposes of allowing interfaces to have some level of restricted changes after publishing. However, until the section “Interface Versioning in C# 8.0 or Later” in this chapter, we will ignore the new capabilities and discuss interfaces for the purposes of establishing polymorphism. This is where the real power of interfaces lies, and it is easier to discuss them in that context before opening up the new capabilities and describing the scenario for making an exception. So let’s stick with the simplification that interfaces cannot have any implementation (without mentioning C# 8.0) and postpone the removal of that restriction until we explore the C# 8.0 capabilities.
The declared members of an interface describe the members that must be accessible on an implementing type. The purpose of non-public 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.1