How to use default interface methods in C# 8.0

Take advantage of default interface methods in C# 8.0 to add new methods to an interface without breaking existing implementations

How to use default interface methods in C# 8.0
Dean Mitchell / Getty Images

One of the new and interesting features in C# 8.0 is the support for default interface methods (also known as virtual extension methods). This article presents a discussion of default interface methods and how we can work with them in C# 8.0.

Until C# 8.0, an interface in C# could not contain method definitions. You could only include method declarations in an interface, and the members of an interface were public and abstract by default. Further, until C# 8.0, an interface could not contain fields, nor could it have private, protected, or internal members. If you introduced a new member (i.e., a method declaration) in an interface, all of your classes that implement the interface would have to be updated.

With C# 8.0, you can now have default implementations of methods in an interface. Interface members can be private, protected, and static as well. Protected members of an interface cannot be accessed in the class that extends the interface. Rather, they can be accessed only in the derived interface. Interface members can also be virtual and abstract. However, virtual members of an interface can be overridden by the derived interface but not by a class that extends the interface. Note that you still can’t include an instance member in an interface.

Why use default interface methods?

Default interface methods are methods in an interface that contain concrete implementations. If the class that extends the interface doesn’t implement the method, the default implementation of the method will be used from the interface. This is a useful feature as it helps developers add methods to future versions of an interface without breaking the existing functionality, i.e., the existing implementations of that interface.

Consider the following interface ILogger.

 public interface ILogger
    {
        public void Log(string message);
    }

The following two classes extend the ILogger interface and implement the Log() method.

 public class FileLogger : ILogger
    {
        public void Log(string message)
        {
            //Some code
        }
    }
public class DbLogger : ILogger
    {
        public void Log(string message)
        {
            //Some code
        }
    }

So far so good. Now, suppose you want to introduce a new method in the ILogger interface that would accept two parameters, the text message to be logged and the log level. The following code snippet shows the log level enumeration.

 public enum LogLevel
    {
        Info, Debug, Warning, Error
    }

Here’s how the ILogger interface looks now.

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

Now, here’s the problem: You’ll be forced to implement this new method in all of the classes that extend the ILogger interface. If you don’t, the compiler will flag an error stating that the new method hasn’t been implemented. Your interface might have been used in several other libraries and even across teams. So, implementing this change will be a painful and perhaps daunting task.

Default interface methods example

This is where default interface methods come to the rescue. You can provide a default implementation for your new Log method as shown in the code snippet given below.

 public interface ILogger
    {
        public void Log(string message);
        public void Log(string message, LogLevel logLevel)
        {
            Console.WriteLine("Log method of ILogger called.");
            Console.WriteLine("Log Level: "+ logLevel.ToString());
            Console.WriteLine(message);
        }
    }

Classes that extend the ILogger interface are no longer required to implement the new Log method. Hence, the following code is valid—the compiler will not throw an error.

 public class FileLogger : ILogger
    {
        public void Log(string message)
        {
            //Some code
        }
    }
 public class DbLogger : ILogger
    {
        public void Log(string message)
        {
            //Some code
        }
    }

Default interface methods are not inherited

Now create an instance of the FileLogger class as shown in the code snippet below, and call the Log method we added newly to the ILogger interface.

FileLogger fileLogger = new FileLogger();
fileLogger.Log("This is a test message.", LogLevel.Debug);
default interface methods c sharp 8 IDG

Default interface methods are not inherited. 

So, this proves that default interface methods are not inherited by the extending class, i.e., the class extending the interface has no knowledge of the default interface methods.

Default interface methods and the diamond problem

And now the million dollar question is, how do default interface methods avoid “the diamond problem,” i.e., how does it resolve issues of multiple inheritance using interfaces? Let’s examine this with an example. Consider the following code listing.

    public interface A
    {
        public void Display();
    }
    public interface B : A
    {
        public void Display()
        {
            Console.WriteLine("Interface B.");
        }
    }
    public interface C : A
    {
        public void Display()
        {
            Console.WriteLine("Interface C.");
        }
    }
    public class MyClass : B, C
    {
    }

When you compile the above program, you will be presented with a compilation error stating that the class MyClass doesn’t implement the interface member A.Display(), and rightly so. Because the Display method lacks a default implementation in interface A, we must provide an implementation in the class MyClass to satisfy the compiler.

Let’s do that. Here is the updated code for your reference.

    public interface A
    {
        public void Display();
    }
    public interface B : A
    {
        public void Display()
        {
            Console.WriteLine("Interface B.");
        }
    }
    public interface C : A
    {
        public void Display()
        {
            Console.WriteLine("Interface C.");
        }
    }
    public class MyClass : B, C
    {
        public void Display()
        {
            Console.WriteLine("MyClass.");
        }
    }

Now create an instance of the class MyClass and call the Display method as shown in the code snippet given below.

static void Main(string[] args)
        {
            A obj = new MyClass();
            obj.Display();
            Console.Read();
        }

Which Display method will be called this time? To avoid ambiguity, the most specific override rule will be applied. Hence, the Display method of the class MyClass will be called.

Abstract classes vs. interfaces in C# 8.0

Are abstract classes and interfaces the same in C# 8.0? Not really. Although abstract classes and interfaces now seem similar in more ways than one, there are subtle differences between the two. You still can’t extend a class from more than one class. And unlike an abstract class, an interface cannot have instance members. Moreover, you still can implement several interfaces.

Default interface methods allow developers to leverage the traits programming technique, a technique that enables you to reuse methods pertaining to unrelated types. Let’s say you have built a library that will be consumed by many developers, and you would like to release a new version of the library. If your interfaces have new members added, you can define their implementations so that you can continue to leverage the types in the library without the need of altering the codebase.

Copyright © 2019 IDG Communications, Inc.