Converting between the Implementing Class and Its Interfaces

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; 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. Consequently, 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.

Interface Inheritance

Interfaces can derive from each other, resulting in an interface that inherits all the members6 in its base interfaces. As shown in Listing 8.6, the interfaces directly derived from IReadableSettingsProvider are the explicit base interfaces.

Listing 8.6: Deriving One Interface from Another
interface IReadableSettingsProvider
{
    string GetSetting(string name, string defaultValue);
}
 
interface ISettingsProvider : IReadableSettingsProvider
{
    void SetSetting(string name, string value);
}
 
public class FileSettingsProvider : ISettingsProvider
{
    #region ISettingsProvider Members
    public void SetSetting(string name, string value)
    {
        // ...
    }
    #endregion
 
    #region IReadableSettingsProvider Members
    public string GetSetting(string name, string defaultValue)
    {
        // ...
    }
    #endregion
}

In this case, ISettingsProvider is derived from IReadableSettingsProvider and therefore inherits its members. If IReadableSettingsProvider also had an explicit base interface, ISettingsProvider would inherit those members as well, and the full set of interfaces in the derivation hierarchy would simply be the accumulation of base interfaces.

Note that if GetSetting() is implemented explicitly, it must be done using IReadableSettingsProvider. The declaration with ISettingsProvider in Listing 8.7 (with Output 8.2) will not compile.

Listing 8.7: Explicit Member Declaration without the Containing Interface (Failure)
// ERROR:  GetSetting() not available on ISettingsProvider
string ISettingsProvider.GetSetting(
    string name, string defaultValue)
{
    // ...
}

Output 8.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) that is derived from a base interface (IReadableSettingsProvider), the class can still declare an implementation of both interfaces overtly, as Listing 8.8 demonstrates.

Listing 8.8: Using a Base Interface in the Class Declaration
public class FileSettingsProvider : ISettingsProvider,
    IReadableSettingsProvider
{
    #region ISettingsProvider Members
    public void SetSetting(string name, string value)
    {
        // ...
    }
    #endregion
 
    #region IReadableSettingsProvider Members
    public string GetSetting(string name, string defaultValue)
    {
        // ...
    }
    #endregion
}

In this listing, there is no change to the interface’s implementations on the class. Although the additional interface implementation declaration on the class header is superfluous, it provides for 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 say that an interface represents a contract, and one contract can 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.

________________________________________

6. Except C# 8.0’s introduced non-private members.
{{ snackbarMessage }}
;