BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Default Interface Methods in C# 8

Default Interface Methods in C# 8

This item in japanese

Bookmarks

Key Takeaways

  • Default interface methods are included in a new feature proposal for C# 8, which will allow developers to use the traits programming technique.
  • Traits are object-oriented programming technology that promotes the reuse of methods between unrelated classes.
  • The C# language developers have based this feature on Java's Default Methods  concept.
  • C# Addresses the diamond inheritance problem that can occur with default interface methods by taking the most specific override at runtime.
  • C# Compiler will try to protect you from making many common implementation errors when utilizing default interface methods.

Default interface methods (also known as virtual extension methods) is a new feature proposal for C# 8, which will allow C# developers to use the Traits programming technique. Traits are object-oriented programming technology that promotes the reuse of methods between unrelated classes.

In this article, I will explain the most important things about this new feature, including the new C# Syntax, and I will show an example of how this feature can make your code more clean and compact.

The main benefit that default methods bring is that now it is possible to add a new default method to an existing interface without breaking the classes that implement that interface. In other words, this feature makes it optional for implementers to override the method or not.

An excellent scenario for this feature is the logging example that is described below; the ILogger interface has one abstract WriteLogCore method. All of the other methods, like WriteError and WriteInformation, are default methods that call the WriteLogCore method with a different configuration. The ILogger implementer has to implement only the WriteLogCore method.

Think of how many lines of code that you have saved in each inherited class of the logger type. While this feature can be a great thing, there are some dangers as it is a form of multiple inheritances.  Hence it suffers from the Diamond Problem, which is described below. Also, the interface methods must be "pure behavior" without the state; that means the interfaces, still, as in the past, cannot directly reference a field.

The C# syntax for an interface in .NET compiler is extended to accept the new keywords in the interfaces which are listed below.  For example, you can write a private method in the interface and the code will still compile and work.

  • A body for a method or indexer, property, or event accessor
  • Private, protected, internal, public, virtual, abstract, override, sealed, static, extern
  • Static fields
  • Static methods, properties, indexers, and events.
  • Explicit access modifiers with default access is public
  • Override modifiers

Not allowed:

  • Instance state, instance fields, instance auto-properties

Default Interface Method Example

Consider this simple example that illustrates how this feature works.

// ------------------------Default Interface Methods---------------------------

  interface IDefaultInterfaceMethod
  {
    public void DefaultMethod()
    {
      Console.WriteLine("I am a default method in the interface!");
    }
  }

  class AnyClass : IDefaultInterfaceMethod
  {
  }

  class Program
  {
    static void Main()
    {
      IDefaultInterfaceMethod anyClass = new AnyClass();
      anyClass.DefaultMethod();
    }
  }

Console Output:

> I am a default method in the interface!

If you look at the code above, you can see that the interface has a default method, and the implementer class contains neither any knowledge of this default method nor an implementation of that interface method.

Change the IDefaultInterfaceMethod to AnyClass, as shown below:

AnyClass anyClass = new AnyClass();
  anyClass.DefaultMethod();

The above code will produce the compile-time error: AnyClass does not contain a member DefaultMethod.

That is the proof that the inherited class does not know anything about the default method.

[Click on the image to enlarge it]

Figure -1- Call by the class error message

To access the default method of the interface, you must upcast it to the interface:

AnyClass anyClass = new AnyClass();
  ((IDefaultInterfaceMethod)anyClass).DefaultMethod(); 

Console Output:

> I am a default method in the interface!

It is also worth mentioning that the same feature has existed for a long time in Java and the .NET team has considered the Java Default Methods documentation as a reference for the .NET Framework Developers, for example:  

"We should look more deeply into what Java does here. There must be accumulated insight already on this topic." - C# Language Design Notes for Apr 11, 2017

Modifiers in Interfaces

As I mentioned before, the C# syntax for an interface is extended to accept the following keywords:  protected, internal, public and virtual. By default, the default interface methods are virtual unless the sealed or private modifier is used. Similarly, abstract is the default on interface members without bodies.

Example:

  // ------------------------ Virtual and Abstract---------------------------

interface IDefaultInterfaceMethod
  {
    // By default, this method will be virtual, and the virtual keyword can be here used!
    virtual void DefaultMethod()
    {
      Console.WriteLine("I am a default method in the interface!");
    }

    // By default, this method will be abstract, and the abstract keyword can be here used
    abstract void Sum();
  }

  interface IOverrideDefaultInterfaceMethod : IDefaultInterfaceMethod
  {
    void IDefaultInterfaceMethod.DefaultMethod()
    {
      Console.WriteLine("I am an overridden default method!");
    }
  }

  class AnyClass : IDefaultInterfaceMethod, IOverrideDefaultInterfaceMethod
  {
    public void Sum()
    {
    }
  }

  class Program
  {
      static void Main()
    {
      IDefaultInterfaceMethod anyClass = new AnyClass();
      anyClass.DefaultMethod();

      IOverrideDefaultInterfaceMethod anyClassOverridden = new AnyClass();
      anyClassOverridden.DefaultMethod();
    }
  }

Console output:

> I am a default method in the interface!
> I am an overridden default method!

The keywords virtual and abstract can be removed from the interface; however, removing them does not have any effect on the compiled code.

Note: No access modifier is permitted in the overridden method.

Override example:

interface IOverrideDefaultInterfaceMethod : IDefaultInterfaceMethod
  {
    public void IDefaultInterfaceMethod.DefaultMethod()
    {
      Console.WriteLine("I am an overridden default method");
    }
  }

The above code will produce the compile-time error: The modifier ‘public’ is not valid for this item.

[Click on the image to enlarge it]

Figure -2- Modifiers are not allowed in the overridden method

Diamond Problem

An ambiguity that can arise because of allowing multiple inheritance. It is a big problem for languages (like C++) that allow multiple inheritance of state. In C#, however, multiple inheritance is not allowed for classes, but rather only for interfaces in a limited way, so that does not contain state.

Figure -3- Diamond Problem dependencies

Consider the following situation:

// ------------------------Diamond inheritance and classes---------------------------

  interface A
  {
    void m();
  }

  interface B : A
  {
    void A.m() { System.Console.WriteLine("interface B"); }
  }

  interface C : A
  {
    void A.m() { System.Console.WriteLine("interface C"); }
  }

  class D : B, C
  {
    static void Main()
    {
      C c = new D();
      c.m();
    }
  }

The above code will produce the compile-time error, which shown below in Figure -4-:

[Click on the image to enlarge it]

Figure -4- Diamond Problem error message

The .NET developer team has decided to solve the Diamond Problem by taking the most specific override at runtime.

"Diamonds with Classes
A class implementation of an interface member should always win over a default implementation in an interface, even if it is inherited from a base class. Default implementations are always a fallback only for when the class does not have any implementation of that member at all."

If you want to know more about this problem, you can find more information in the Proposal: Default Interface Methods and C# Language Design Notes for Apr 19, 2017

Back to our example, the problem is that the most specific override cannot be inferred from the compiler. However, you can add the method ‘m’ in the class ‘D’ as shown below, and now the compiler uses the class implementation to solve the diamond problem.

  class D : B, C
  {
    // Now the compiler will use the most specific override, which is defined in the class ‘D’
    void A.m()
    {
       System.Console.WriteLine("I am in class D"); 
    }

    static void Main()
    {
      A a = new D();
      a.m();
    }
  }

Console Output:

> I am in class D


This Keyword
The code below is an example which shows how to use ‘this’ keyword in the interfaces.

public interface IDefaultInterfaceWithThis
  {
    internal int this[int x]
    {
      get
      {
        System.Console.WriteLine(x);
        return x;
      }
      set
      {
        System.Console.WriteLine("SetX");
      }
    }

    void CallDefaultThis(int x)
    {
      this[0] = x;
    }
  }

  class DefaultMethodWithThis : IDefaultInterfaceWithThis
  {
  }

Using the code:

  IDefaultInterfaceWithThis defaultMethodWithThis = new DefaultMethodWithThis();
  Console.WriteLine(defaultMethodWithThis[0]);  
  defaultMethodWithThis.CallDefaultThis(0); 

Console Output:

0
SetX

The ILogger Example

The logger interface is the most used example to explain the Default methods technique. In my code example below which contains one abstract method named "WriteCore". The other methods have a default implementation. ConsoleLogger and TraceLogger implementing the interface. If you look at the code below, you can see that the code is compact and clean. In the past, it was mandatory to implement the methods in a class that implements an interface unless that class is an Abstract class, and this might make your code DRY.  With the new approach, the class ConsoleLogger will be able to inherit from another class hierarchy, in other words, using default methods will give you the most flexible design.

  enum LogLevel
  {
    Information,
    Warning,
    Error
  }

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

    void WriteInformation(string message)
    {
      WriteCore(LogLevel.Information, message);
    }

    void WriteWarning(string message)
    {
      WriteCore(LogLevel.Warning, message);
    }

    void WriteError(string message)
    {
      WriteCore(LogLevel.Error, message);
    }
  }

  class ConsoleLogger : ILogger
  {
    public void WriteCore(LogLevel level, string message)
    {
      Console.WriteLine($"{level}: {message}");
    }
  }

  class TraceLogger : ILogger
  {
    public void WriteCore(LogLevel level, string message)
    {
      switch (level)
      {
        case LogLevel.Information:
          Trace.TraceInformation(message);
          break;

        case LogLevel.Warning:
          Trace.TraceWarning(message);
          break;

        case LogLevel.Error:
          Trace.TraceError(message);
          break;
      }
    }
  }

Using the code:

      ILogger consoleLogger = new ConsoleLogger();
      consoleLogger.WriteWarning("Cool no code duplication!");  // Output: Warning: Cool no Code duplication!

      ILogger traceLogger = new TraceLogger();
      consoleLogger.WriteInformation("Cool no code duplication!");  // Cool no Code duplication!

The Player Example

A game, which contains different types of players. The power player causes the most attack damage, on the other side, the limited player can cause less damage.  

  public interface IPlayer
  {
    int Attack(int amount);
  }

  public interface IPowerPlayer: IPlayer
  {
    int IPlayer.Attack(int amount)
    {
      return amount + 50;
    }
  }

  public interface ILimitedPlayer: IPlayer
  {
    int IPlayer.Attack(int amount)
    {
      return amount + 10;
    }
  }

  public class WeakPlayer : ILimitedPlayer
  {
  }

  public class StrongPlayer : IPowerPlayer
  {
  }

Using the code:

  IPlayer powerPlayer = new StrongPlayer();         
  Console.WriteLine(powerPlayer.Attack(5));  // Output 55

  IPlayer limitedPlayer = new WakePlayer();
  Console.WriteLine(limitedPlayer.Attack(5));  // Output 15

As you see in the code example above, the default implementation is in the IPowerPlayer interface, and in the ILimitedPlayer interface. The limited player doing less damage. If we define a new class, for example, SuperDuperPlayer, which inherited from the class StrongPlayer, then the new class automatically receives the default power attack behavior from the interface, as shown in the example below.

  public class SuperDuperPlayer: StrongPlayer
  {
  }

  IPlayer superDuperPlayer = new SuperDuperPlayer();
  Console.WriteLine(superDuperPlayer.Attack(5)); // Output 55

The Generic Filter Example

ApplyFilter is a default interface method that contains a predicate which is applied to a generic type. In my example, the dummy filter is used to simulate mocked behavior. 

  interface IGenericFilter<T>
  {
    IEnumerable<T> ApplyFilter(IEnumerable<T> collection, Func<T, bool> predicate)
    {
      foreach (var item in collection)
      {
        if (predicate(item))
        {
          yield return item;
        }
      }
    }
  }

  interface IDummyFilter<T> : IGenericFilter<T>
  {
    IEnumerable<T> IGenericFilter<T>.ApplyFilter(IEnumerable<T> collection, Func<T, bool> predicate)
    {
      return default;
    }
  }

  public class GenericFilterExample: IGenericFilter<int>, IDummyFilter<int>
  {
  }

Using the code:

      IGenericFilter<int> genericFilter = new GenericFilterExample();
      var result = genericFilter.ApplyFilter(new Collection<int>() { 1, 2, 3 }, x => x > 1);

Console output:

2, 3

      IDummyFilter<int> dummyFilter = new GenericFilterExample();
      var emptyResult = dummyFilter.ApplyFilter(new Collection<int>() { 1, 2, 3 }, x => x > 1);

Console output:

0

You can use this generic filter concept for many other designs and needs.

Limitations

There are some limitations and considerations that you first need to understand when using modifier keywords in the interfaces. In many cases, the compiler protects you against your mistakes by detecting a lot of common errors such as those listed below.

Consider the following Code:

  interface IAbstractInterface
  {
    abstract void M1() { }
    abstract private void M2() { }
    abstract static void M3() { }
    static extern void M4() { }
  }

  class TestMe : IAbstractInterface
  {
    void IAbstractInterface.M1() { }
    void IAbstractInterface.M2() { }
    void IAbstractInterface.M3() { }
    void IAbstractInterface.M4() { }
  }

The above code will produce the below listed compile-time errors:

error CS0500: 'IAbstractInterface.M1()' cannot declare a body because it is marked abstract
error CS0621: 'IAbstractInterface.M2()': virtual or abstract members cannot be private
error CS0112: A static member 'IAbstractInterface.M3()' cannot be marked as override, virtual, or abstract
error CS0179: 'IAbstractInterface.M4()' cannot be extern and declare a body
error CS0122: 'IAbstractInterface.M2()' is inaccessible due to its protection level

error CS0500, means the default method 'IAbstractInterface.M3()' cannot be abstract and at the same time, it has a body. error CS0621, means the method cannot be private and at the same time abstract.

In Visual Studio:

[Click on the image to enlarge it]

Figure -5- Compilation Errors in Visual Studio

Source code repositories and other developer resources

More information and source code:

About the Author

Bassam Alugili is Senior Software Specialist and databases expert at STRATEC AG. STRATEC is a world-leading partner for fully automated analyzer systems, software for laboratory data management, and smart consumables.

 

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

  • Opinion on default interface methods

    by Pawel Kedzior,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I have mixed feelings about this concept. In large-scale projects, it's very important to separate contracts from implementation, so I encourage developers to come up with contract libraries which consist of interfaces, POCO classes and enums only and keep them separated from implementation libraries.

    I would much prefer the language allow for easier enforcement of avoiding polluting contracts with implementation details. I perceive default interface methods as a step backward from this idea and giving a green light to smuggle implementation into contracts.

  • Re: Opinion on default interface methods

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Thank you for your comment. The idea behind the separation between contracts and implementation is to maximize the Agility and the Maintainability.

    For example, if your DAL based on Repository pattern. You will have something like Repostiory.Contracts.dll and Repsotiory.dll. Let’s say Repsotiory.dll = RepositoryEntityFramewrok.dll is based on ORM Tool like Entity Framework (used to load and store the POCO’s).
    Because you have separate the interfaces from the implementation, it is easier to replace RepositoryEntityFramewrok.dll with RepositoryNHibernate.dll. For this reason, the contracts separation is essential for some projects.

    The C# 8 support a lightweight form of the multiple inheritances. This inheritance type has no access to the object state (implementation details). That means you still can replace the assembly(RepositoryNHibernate.dll) as before and the code still clean as you wish.

    And finally look please deeper to the Logging example and do it with the old coding style, then you will see directly the benefits of this technique.

  • Re: Opinion on default interface methods

    by Pawel Kedzior,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Thank you, Bassam for the response.

    The example with the logger is certainly convincing. So far such situations are handled by introducing a second interface which derives from the older one. Then slowly both clients and implementations need to be migrated.

    This process is painful and frequently never gets completed, so the code remains in a dirty/half-migrated state forever. Code readability and maintainability suffer. Additionally, rarely there is a good name for the new interface, so people tend to suffix it with a number. In your example that may be ILogger2.

    I can clearly see that this issue is addressed by iface default implementations.

  • Of course it comes from Java

    by Jay Jeckel,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    So, .Net is now looking at Java as an acceptable source of guidance? Alright, everyone, its been a good ride, but it is time to close up shop.

    But seriously, all this is doing is muddying the waters between interfaces and abstract classes. I fail to see how this improves the language in any real, substantial way. I've been an avid .Net proponent for many years, but lately I'm losing confidence that the dev team has any idea where C# is going and it looks like they are actually starting to add features that lessen clarity, consistency, readability, and maintainability.

    Abandon this dumb feature and fire everyone who even hinted that Java should have any influence on C#.

  • Re: Of course it comes from Java

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I know that the developers are very worried about how the .Net currently is developing. A lot of small and fast features and releases and some of these features are ambiguous or uncertain. However, the world is changing with a lot of new challenges like FP, etc.

    If the programming language is not able to adapt this evolution, then it will die. This is the chaining time and in this time is the most painful for developers after few months/years everything will be back to normativity.

    This is the general rule for all programming languages also for Java. Look for LINQ or Rx how those Microsoft concept are currently working perfect.

  • Re: Of course it comes from Java

    by Leonardo Vargas,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    @Jay Jeckel

    Well there are a lot of similarities, and when C# was done, it was build using the concepts of many many languages including Java, but Java is not only a language, it is a platform, and well these are the contributions that Java did to the .net ecosystem:

    Virtual Machine in this case the CLR that support Garbage Collector, JIT, and the intermediate language (kind of bytecodes that is object oriented).

    No header files in the c# classes

    The syntax is more or less from C++ or any other language but it has a lot of the same syntax that java has, and they were inspired by c, and c++, in the original design they took a lot of best practices from a lot of languages.

    Simplicity to write in c++ or in c programming languages require a lot of knowledge, and in Java this was remove, and well this was one concept that they took to make the c# compiler more user friendly. No headers, No pointers without control, the language was build in a manage environment as the Java language with the JVM.

    In nowadays C# is a multi paradigm programming language that takes a lot of features from a lot of languages including Java, and I am not only talking about extends or implements I am talking about all the language syntax.

    For example the properties idea were taken from VB, the lambdas and closures were taken from the functional languages and well you could see a lot of good features that C# has today, and these features were taken from other languages.

    Even C# is kind of monster, because it has a lot of paradigms: functional, object oriented, dynamic, and so on, so from a language perspective this is mix of features that just could complicate the language, but people love them and use to work with all of them, so I do not think that a little more influence from Java will destroy C# maybe it could improve.

    Remember Java has a lot of the enterprise and it is in the mobile ecosystem as well, so is a huge language with very good community and some times very good ideas as well.

  • Re: Of course it comes from Java

    by Cameron Purdy,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    "So, .Net is now looking at Java as an acceptable source of guidance? Alright, everyone, its been a good ride, but it is time to close up shop."


    C# was originally part of a clean-room Java implementation project ("Cool"), so yes, .NET has definitely pulled quite a lot (initially at least 95%) from Java.

    Java was relatively stagnant for a while, and C# continued to evolve, so then Java started pulling quite a bit from C#.

    Now they're pulling ideas from each other, in a race to the entropy limit.

  • Typo in code

    by Mike Hibbert,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    appologies if already reported, minor typo "IDefualtInterfaceMethod" in secton "Modifiers in Interfaces"

  • Re: Typo in code

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Thank you! I will fix that!

  • Re: Of course it comes from Java

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Very good! "Uncle" Bob Martin - "The Future of Programming"

    www.youtube.com/watch?v=ecIWPzGEbFc

    Uncule Bob, has explained the future in a very nice way!

  • Re: Of course it comes from Java

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Very good! and I recommend to watch this video: "Uncle" Bob Martin - "The Future of Programming"

    Uncule Bob, has explained the future in a very nice way!

  • Re: Opinion on default interface methods

    by Jonathan Allen,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I have to disagree. The logger example is a textbook example of what should be implemented as an abstract base class.

    Making it an interface instead of an abstract class unnecessarily limits your future options (e.g. no fields) without any corresponding benefit.

  • Re: Opinion on default interface methods

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Thank your for your replay! The benefit less code and you do not need the base abstract class and I hope that you do not also need fields in the future (Separation between behaviours and states is a nobel goal) if you must do that you are still able to make the core method as abstract within the base abstract class which contains the fields(state).

  • Re: Opinion on default interface methods

    by Carlos Nogueira,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Yeah, I had the same feeling. So, I think you can solve the same problem (at least, in the most cases) using abstract class concept with the same clean code, except to inherited classes using multiples inherantance. I did not see, at the moment, too many advantages between abstract class and default interface method.

  • Re: Opinion on default interface methods

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    :) Thank you for your feedback! Clean Code Core is Testability, without the base class everything is better testable and mockable! and the less code/clasess enhance the readability. If you do not believe in that the other use case is think about if you have a customer which has developed a library which is implementing/extending some interfaces from your code and you do not have the code for the customer and assume you need to add a generic function to the interface which has no effect on the customer code! in the past this is not possible beacuse any change in the interface means you have to rebuild the customer library. although the changes does not effect the customer code I have to do that becuase the interface is changed. DIM is a compromise to solve those problems. In Medical field any change in the cusotmer library means for us really a lot of money for verification and validation and this might be a good soultion for us. Sometimes it is not possible to change the cusotmer code at all! With this Controversial new feature we can change the interface (limited changes) ! and without to have to build the customer code.

  • Default Interface Methods

    by Bassam Alugili,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I just to add more information about DIM. !!!This feature is controversial!!! I did not handled it in the critical way. Because of this feature does not break the interface contract concept. Syme's (F# Creator) and other big names are against this feature (github.com/fsharp/fslang-suggestions/issues/679...). C# is multi paradigm programming language and I do not see any reasonable reason to forbid this fearure in C#. My tip just think before you are use it and compare your solution with the other alternative techniques.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT