Default Interface Methods Coming to C# 8

Posted On: 7/11/2018 7:54:41 PM

Filed Under: Programming


With the release of a new version of C# just over the horizon, many developers have been digging into the upcoming features and offering their opinions. Despite the many new features to be excited about: Null Reference Types, built-in Async Streams, Range operators, and more, none seem to be garnering as much attention as allowing default methods to be implemented at the Interface level. Of course the obvious reason for this chatter is: this change moves C# towards the direction of multiple inheritance (and the problems that may go with it). But C# is fundamentally a single-inheritance language. Many view that as a feature! So why the change? Here are the principal motivations that moved this feature forward:

  • Default interface methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

  • The feature enables C# to interoperate with APIs targeting Android (Java) and iOS (Swift), which support similar features.

  • Adding default interface implementations provides the elements of the "traits" language feature.

Future Method Implementations

This first bullet-point was enough to have me on Amazon.com ready to buy my C# 8 team jersey. Once an interface is implemented by an Object, changing that interface becomes an issue of breaking code. Across a small code-base this may be reasonable to manage. If it’s across a medium-to-large-sized code base you are out of your mind!

Currently, the only way to manage this would be creating a new interface that implements the old interface. For example:

Our code base has an ITextWriter interface that is implemented by any object that displays text to the screen. The development team wants this contract to now include writing to a pdf. How do we facilitate this change?

interface ITextWriter
{
  void WriteToScreen();
}
/* 
    We now want to expand this to write to a pdf.  If we add the method directly, 
    all Classes that implement this interface will automatically break, to avoid
    that we extend the interface like so:
*/
interface IEvenBetterTextWriter : ITextWriter
{
  void WriteToPdf();
}

Imagine how bulky and unmanageable this can become over time. With a default method implementation, you write in some default behavior, avoid breaking code, and override the default behavior piece-meal.

Interoperability

 

Traits Programming Technique

Traits Programming seeks to do two things: solve the problems associated with multiple inheritance while extending the utility of single inheritance languages.

As Steve Cook noted in 1987:


“Multiple inheritance is good, but there is no good way to do it.”

Single inheritance has its limitations that can be alleviated by use of interfaces. Interfaces, however, can sometimes force code duplication. For example:

We have two Interfaces. One is ITextWriter and the other is IDisplayAdapter. The IDisplayAdapter is implemented by any class that can display information to the user. The ITextWriter simply wants to display its' text onto whatever medium the IDisplayAdapter provides. So, in this instance:

interface ITextWriter 
{
  IDisplayAdapter Adapter { get; set; }
  string Text { get; set; }
  
  void WriteText();
}

We would have to duplicate the WriteText() method in every single class that inherits the ITextWriter interface. Even though every single implementation may look like:

public void WriteText()
{
  Adapter.Print(Text);
}

This anti-DRY inefficiency is acknowledged by the Traits technique, and can be solved by default method implementations. Not to mention, method overrides are still on the table as well as Reabstraction, should the default implementation not immediately apply.

 

 


Comments