Default implementations in interfaces

Mads Torgersen

Mads

Default implementations in interfaces

With last week’s posts Announcing .NET Core 3.0 Preview 5 and Visual Studio 2019 version 16.1 Preview 3, the last major feature of C# 8.0 is now available in preview.

A big impediment to software evolution has been the fact that you couldn’t add new members to a public interface. You would break existing implementers of the interface; after all they would have no implementation for the new member!

Default implementations help with that. An interface member can now be specified with a code body, and if an implementing class or struct does not provide an implementation of that member, no error occurs. Instead, the default implementation is used.

Let’s say that we offer the following interface:

interface ILogger
{
    void Log(LogLevel level, string message);
}

An existing class, maybe in a different code base with different owners, implements ILogger:

class ConsoleLogger : ILogger
{
    public void Log(LogLevel level, string message) { ... }
}

Now we want to add another overload of the Log method to the interface. We can do that without breaking the existing implementation by providing a default implementation – a method body:

interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());
}

The ConsoleLogger still satisfies the contract provided by the interface: if it is converted to the interface and the new Log method is called it will work just fine: the interface’s default implementation is just called:

public static void LogException(ConsoleLogger logger, Exception ex)
{
    ILogger ilogger = logger; // Converting to interface
    ilogger.Log(ex);          // Calling new Log overload
}

Of course an implementing class that does know about the new member is free to implement it in its own way. In that case, the default implementation is just ignored.

The best way to get acquainted with default implementations is the Tutorial: Update interfaces with default interface members in C# 8 on Microsoft Docs.

Happy hacking!

Mads

65 comments

Comments are closed.

  • Avatar
    cheong00

    Does it mean we can now define private member in interfaces?
    That’s because as far as I understands, the default implementation would still required to use defined member of the interface only, and IMO in the most cases, useful default implementation would need access to some private members. (This is the primary reason why we choose base classes over interface when need to choose between them)

    • Avatar
      Collin Jasnoch

      The example given does not imply private members being allowed and shows an example of where it is used and prvate members are not needed. Your assumption is just wrong. While it may appear that in your case you need private members, that is likely the line. If you are storing state it should be stored in the class heirarchy.

      • Avatar
        cheong00

        No. I’m just arguing the usefulness of implementation like this, because similar effect has already been achievable with extension method.
        On the other hand, if there can be private member defined in inteface for this default implementation to interact with, there can be some really useful things be done.
        We all know that the current defination of interface does not allow defining private members, so I put another suggestion that instead of providing default implementation on the interface itself, why don’t we specify a default implementation base class that provides all the methods which we want to provide default implementation? Of course instead of private and protected, we may need new accessibility modifier for this kind of “private member”.

  • Avatar
    cheong00

    For some reason, I hope you can find it worth considering to allow interfaces to specify base class for default implementations. That base class could be abstract class that provides implementation for methods that it want to provide, and for members which the class wouldn’t provide default implmentation it can just specify them as abstract members. In this way we can somehow solve the “multiple base class” problem – either need to explicitly cast to interface when ambiguity is found, or find some way to define proper resolve order of interface.

  • Neil Walker
    Neil Walker

    When you over engineer something it doesn’t get better, it starts to become a mess.

    There’s a reason why it’s an interface, if your want code, user an abstract class. 

    You’re re just trying to fix an edge case by ruining a simple principle. Just like java did with enums. 

  • Avatar
    Xavier Poinas

    This seems quite sloppy to me. Isn’t this what abstract classes are for?
    With those changes, what’s the difference between an interface and an abstract class?
    The only one I see is that a class can only inherit one (abstract) class but can implement several interfaces. The restriction on having only one base class was to avoid the typical issues associated with multiple inheritance. With interfaces behaving like classes aren’t we back to multiple inheritance?

    • Avatar
      Alfonso Ramos Gonzalez

      C# has multiple – interface – inheritance that is nothing new.

      I guess you are worried about the classic diamond problem. Well, today, without C# 8.0, you can have a class that implements multiple interfaces with members of the same name. You can have them separate simply by having them implemented explicitly.

      I suppose the source of this worry comes from C# obscuring the fact that the in CLR all interface member implementations are explicit (this is clean in VB.NET where you need the “implements” statement). With the interface default implementation that is not changing, they will be explicit.

      No new public members will be exposed by classes implementing them (unless the author of those classes changes the code to implement them), thus, these members will only be accesible after a cast to the interfaces (for example, simply by passing the object to a method that takes the interface instead of the class), and since you are accesing via the interface, there is no ambiguity.

      For a class that has not been modified to implement the new interface members that have a default implementation, they function like extension methods (after all, the default implementation is in terms of the other members of the interface). And thus, they do not pose any threat to the state of the class. And of course, the author can choose to provide an implementation for these members in a future version.

      As described in the article, this arrangement allows authors to extend interfaces without breaking client code.

  • Avatar
    Charles Roddie

    People worry that this will lead to clean code resulting from multiple inheritance. In that case it may be better to give a warning on creation of these interfaces, which can be disabled when writing Java interop code. (It is said that the real reason for this feature is Java interop. )

  • Avatar
    Yves Goergen

    Does this require changes to the .NET assembly file format? I.e. will post-compilation tools have to be updated to support this feature? From my past experience working with APIs like Cecil, I don’t see how this feature would have been possible before.

    • Avatar
      Matt Warren

      > Does this require changes to the .NET assembly file format?I was fortunate to have a chance to chat with Mads about this exact question at a conference a while back. He explained to me that there’s no .NET assembly file format change, you’ve always been able to (in metadata) associate a function with an interface, it’s just that the runtime has then blocked this, by throwing an error when you load an interface in this scenario.So what’s happened now is that this restriction has been relaxed. Of course, there’s a lot of other work in the runtime to make it possible, but in terms of assembly file format, there’s not needed to be a change.(I hope that I’m remembering the conversation correctly!!)I guess that some tools will still have to be updated to recognise this scenario and do the right thing, i.e. show that there is a method associated with an interface.