Extension Methods versus Default Interface Members

When it comes to extending a published interface with additional functionality, when is a default interface member preferable to creating an extension method or creating a second interface that derives from the first and adds additional members? The following factors should be considered when making this decision:

Both conceptually support overriding by implementing a method of the same signature on an instance of the interface.
Extension methods can be added from outside the assembly that contains the interface definition.
While default interface properties are allowed, there is no instance storage location available (fields are not allowed) for the property value, limiting the applicability to calculated properties.
While there is no support for extension properties, calculations can be provided with “getter” extension methods (e.g., GetData()), without limiting them to .NET Core 3.0 or later frameworks.
Providing a second derived interface supports defining both properties and methods without introducing a version incompatibility or a framework limitation.
The derived interface approach requires implementing types to add the new interface and take advantage of the new capability.
Default interface members can be invoked only from the interface type. Even implementing objects don’t have access to the default interface members without casting to the interface. In other words, default interface members behave like explicitly implemented interface members unless the base class provides an implementation.
Protected virtual members can be defined on interfaces, but they are only available as such to deriving interfaces—not to classes implementing the interface.
A default interface member may be overridden by the implementing classes, thereby allowing each class to define the behavior if desired. With extension methods, the binding is resolved based on the extension method being accessible at compile time. As a result, the implementation is determined at compile time instead of at runtime. With extension methods, therefore, the implementing class author can’t provide a different implementation for the method when it’s called from libraries. For example, System.Linq.Enumerable.Count() provides a special implementation index-based collection by casting to the list implementation to retrieve the count. As a result, the only way to take advantage of the improved efficiency is to implement a list-based interface. In contrast, with a default interface implementation, any implementing class could override this method to provide a better version.

In summary, adding property polymorphic behavior is only possible with a second interface or default interface members. When only methods, and not properties, are part of the updated interface, extension methods are preferred.

Guidelines
CONSIDER using extension methods or an additional interface in place of default interface members when adding methods to a published interface.
DO use extension methods when the interface providing the polymorphic behavior is not under your control.
Interfaces Compared with Abstract Classes

Interfaces introduce another category of data types. (They are one of the few categories of types that don’t extend System.Object.9) 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 instance constructors or finalizers. Prior to C# 8.0, static members are not allowed on interfaces.

Interfaces are similar to abstract classes, sharing such features as the lack of instantiation capability. Table 8.2 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 8.2 and the guidelines that follow to make the right choice.

Table 8.2: Comparing Abstract Classes and Interfaces

Abstract Classes

Interfaces

Cannot be instantiated directly, but only by instantiating a non-abstract derived class.

Cannot be instantiated directly, but only by instantiating an implementing type.

Derived classes either must be abstract themselves or must implement all abstract members.

Implementing types must implement all abstract interface members.

Can add additional non-abstract members that all derived classes can inherit without breaking cross-version compatibility.

Can add additional default interface members in C# 8.0/.NET Core 3.0 that all derived classes can inherit without breaking cross-version compatibility.

Can declare methods, properties, and fields (along with all other member types, including constructors and finalizers).

Instance members are limited to methods and properties, not fields, constructors, or finalizers. All static members are possible, including static constructors, static events, and static fields.

Members may be instance or static, and optionally abstract, and may provide implementations for non-abstract members that can be used by derived classes.

Starting with C# 8.0/.NET Core 3.0, members may be instance, abstract, or static, and may provide implementations for non-abstract members that can be used by derived classes.

Members may be declared as virtual or not. Members that should not be overridden (see Listing 8.13) would not be declared as virtual.

All (non-sealed) members are virtual, whether explicitly designated as such or not; therefore, there is no way for an interface to prevent overriding the behavior.

A derived class may derive from only a single base class.

An implementing type may arbitrarily implement many interfaces.

Static abstract members are not supported.

C# 11 introduced static abstract members.

Guidelines
CONSIDER defining an interface if you need to support its functionality on types that already inherit from some other type.

In summary, assuming a .NET Core 3.0 framework is acceptable, C# 8.0 (or later) defined interfaces have all the capabilities of abstract classes except the ability to declare an instance field. Given that an implementing type can override an interface’s property to provide storage, which the interface then leverages, interfaces virtually provide a superset of what an abstract class provides. Furthermore, interfaces support a more encapsulated version of protected access in addition to multiple inheritance. Regardless, default interface members are intended for versioning not polymorphism so use caution when leveraging them for the latter.

C# 11 added support for static abstract members that would be included in the contract that implementing interfaces would need to implement. Since these don’t become fully functional without generics, we postpone a discussion on this topic until Chapter 12.

________________________________________

9. The others are 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 perhaps this is a hairsplitting distinction.
{{ snackbarMessage }}
;