Interface Implementation

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 derive directly from only one base class. An example appears in Listing 8.3.

Listing 8.3: Implementing an Interface
public class Contact : PdaItem, IListable, IComparable
{
    // ...
 
    #region IComparable Members
    /// <summary>
    /// 
    /// </summary>
    /// <param name="obj"></param>
    /// <returns>
    /// Less than zero:      This instance is less than obj
    /// Zero                 This instance is equal to obj 
    /// Greater than zero    This instance is greater than obj 
    /// </returns>
    public int CompareTo(object? obj) => obj switch
    {
        null => 1,
        Contact contact when ReferenceEquals(this, obj) => 0,
        Contact { LastName: string lastName } 
            when LastName.CompareTo(lastName) != 0 =>
                LastName.CompareTo(lastName),
        Contact { FirstName: string firstName } 
            when FirstName.CompareTo(firstName) != 0 =>
                FirstName.CompareTo(firstName),
        Contact _ => 0,
        _ => throw new ArgumentException(
            $"The parameter is not a value of type nameof(Contact) }",
            nameof(obj))
    };
    #endregion
 
    #region IListable Members
    string?[] IListable.CellValues
    {
        get
        {
            return new string?[]
            {
                FirstName,
                LastName,
                Phone,
                Address
            };
        }
    }
    #endregion
 
    // ...
}

Once a class declares that it implements an interface, all (abstract2) members of the interface must be implemented. An abstract class is permitted to supply an abstract implementation of an interface member. A non-abstract implementation may throw a NotImplementedException type exception in the method body, but an implementation of the member must always be supplied.

One important characteristic of interfaces is that they can never be instantiated; you cannot use new to create an interface, so interfaces do not have instance constructors or finalizers. Interface instances are available only by instantiating a type that implements the interface. Furthermore, interfaces cannot include static members.3 One key interface purpose is polymorphism, and polymorphism without an instance of the implementing type has little value.

Each (non-implemented4) interface member is abstract, forcing the derived class to implement it. Therefore, it is not possible to use the abstract modifier on interface members explicitly.5

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.

Explicit Member Implementation

Explicitly implemented methods are available only by calling them through the interface itself; this is typically achieved by casting an object to the interface. For example, to call IListable.CellValues in Listing 8.4, you must first cast the contact to IListable because of CellValues’ explicit implementation.

Listing 8.4: Calling Explicit Interface Member Implementations
string?[] values;
Contact contact = new("Inigo Montoya");
 
// ...
 
// ERROR:  Unable to call .CellValues directly
//         on a contact
values = contact.CellValues;
 
// First cast to IListable
values = ((IListable)contact).CellValues;
 
// ...

The cast and the call to CellValues occur within the same statement in this case. Alternatively, you could assign contact to an IListable variable before calling CellValues.

To declare an explicit interface member implementation, prefix the member name with the interface name (see Listing 8.5).

Listing 8.5: Explicit Interface Implementation
public class Contact : PdaItem, IListable, IComparable
{
    // ...
 
    #region IListable Members
    string?[] IListable.CellValues
    {
        get
        {
            return new string?[] 
            {
                FirstName,
                LastName,
                Phone,
                Address
            };
        }
    }
    #endregion
 
    // ...
}

Listing 8.5 implements CellValues 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. 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.

Note that even though the override keyword is not allowed on an interface, we will still use the term “override” when referring to members that implement the interface-defined signature.

Implicit Member Implementation

Notice that CompareTo() in Listing 8.5 does not include the IComparable prefix; it is implemented implicitly. With implicit member implementation, it is necessary only 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 use of the override keyword or any indication that this member is tied to the interface. Furthermore, since the member is declared just like 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.

Explicit versus Implicit Interface Implementation

The key difference between implicit and explicit member interface implementation lies not in the syntax of the method declaration but rather 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; instead, 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 cast the object to an interface such as IComparable before treating the object as comparable explicitly separates in the code the concepts of talking to the model and 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. In the real world, for example, 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()] in .NET. 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 implementation and an implicit implementation.

Is the member a core part of the class functionality?
Consider the CellValues property implementation on the Contact class. This member is not an integral part of a Contact type, but rather 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: 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 which operation the method performs. Instead, it is preferable to implement the member explicitly so that the Dump() method can be called only from a data type of ITrace, where the meaning is clearer. Consider using an explicit implementation if a member’s purpose is unclear on the implementing class.
Does a class member with the same signature already exist? 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-making regarding implicit versus explicit interface member implementation comes down to intuition. However, these questions provide suggestions about which issues 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 implementations later. 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.

________________________________________

2. The capability of adding non-abstract members to an interface was added in C# 8.0 but is essentially ignored until the end of the chapter.
3. Before C# 8.0.
4. Implemented members were only became available in C# 8.0 or later.
5. Before C# 8.0.
{{ snackbarMessage }}