Default Interface Implementation

C# 8 is getting released soon along with .NET Core 3.0 and it has several new features and enhancements to give more power to developers and make their day to day coding even easier. At the time of writing this article, C# 8 is available as preview version 5.
 
I am starting a series to go through the new C# 8 features one by one to cover all the new features and enhancements.
 
In this article, we will go through the default implementations of interface members; however, first, let’s have a look at the setup required to work on C# 8.
 

Prerequisite to use the C# 8 (Preview Version)

  • Visual Studio 2019
  • Change the Language Version setting

    • Right-click on your project, click Properties, click Build, click Advanced and then select “C# 8.0 (beta)” in the Language Version.

      Working With Default Interface Implementation In C# 8.0
  • Enable the .NET Core SDK

    • In the menu bar, click Tools, click Options, expands Environment, click Preview Features and then check the box that says “Use Previews of the .NET Core SDK” as follows.

      Working With Default Interface Implementation In C# 8.0

Background and Use-Case

 
Default Interface Implementation is based on Traits programming technique where reuse of methods between unrelated classes is promoted. This is also available on other programming languages, for example, its present is java in the name of Default Methods.
 
Now, let’s try to understand the use cases that this feature is trying to solve.
 
Consider a case when you want to add some interface method and you fear that such action can lead to implementing that method in all the concrete classes.
 
The default implementation of the interface is here to address the situation mentioned above as it allows us to add interface members with implementation or code block. This feature is designed in such a way that concrete classes have no clue on the existence of default interface and hence there is no need to implement such members.
 
Default interface implementation is useful especially in cases when you want to add additional contracts gracefully to accommodate new requirements without disturbing the existing contracts.
 
Point to note here is if you don't provide implementation to interface members, it will be treated as regular interface member and classes implementing interface needs to provide implementations to all its members.
 

Implementation

 
Let's take an example to understand how to work with it.
  1. interface ILogger  
  2.     {  
  3.         void Log(LogLevel level, string message);  
  4.         void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());  
  5.     }  
  6.   
  7. class CustomLogger : ILogger  
  8.     {  
  9.         public void Log(LogLevel level, string message)  
  10.         {  
  11.             Console.WriteLine($"{level.ToString()}: Hello readers of C# 8!! {message}");  
  12.         }         
  13.     }  
  14.   
  15. class DefaultInterfaceImpl  
  16.     {  
  17.         public static void DoDivide()  
  18.         {  
  19.             try  
  20.             {  
  21.                 int a = 3;  
  22.                 int b = 0;  
  23.                 int c = a / b;  
  24.             }  
  25.             catch (DivideByZeroException ex)  
  26.             {  
  27.                 ILogger logger = new CustomLogger(); // Converting to interface                 
  28.                 logger.Log(ex); // Calling new Log overload  
  29.                        }  
  30.                }  
  31.       }  
As you can see in the code above that default interface member is invoked from catch block despite concrete class CustomLogger has not implemented it. Before calling the Log method, the class instance is converted to the ILogger interface.
 
Now let's try to call the Log method only with CustomLogger class as following.
  1. CustomLogger logger2 = new CustomLogger();   
  2. logger2.Log(ex);  
The above code will result in a compile-time error that "There is no argument given that corresponds to the required formal parameter 'message' of 'CustomLogger.Log(LogLevel, string)" simpy because CustomLogger class has no information about default interface members.
 
Now, let's go a little deeper on the internals of default interface implementation and have a look at the modified interface as following.
  1. interface ILogger2  
  2.     {  
  3.         abstract void Log(LogLevel level, string message);  
  4.         virtual void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());  
  5.     }  
The above code will give compile-time errors in previous versions of C# 8 as following.
 
Working With Default Interface Implementation In C# 8.0
 
And, this code will perfectly compile in C# 8 and the reason is in C# 8 regular interface members are classified as abstract and default interface members are designed as virtual and hence declaring them as abstract or virtual doesn't throw any error.
 
Making default interface members as virtual gives the flexibility to extend their implementations based on the requirement.
 
To have a look at the same in action let's add a new class by implementing the ILogger2 interface as follows.
  1. class CustomLogger2 : ILogger2  
  2.         {  
  3.             public void Log(LogLevel level, string message)  
  4.             {  
  5.                 Console.WriteLine($"{level.ToString()}: Hello readers of C# 8!! {message}");  
  6.             }  
  7.             public void Log(Exception message)  
  8.             {  
  9.                 Console.WriteLine($"From: Overridden method: Hello readers of C# 8!! {message}");  
  10.             }  
  11.         }  
And add a new method in an existing class DefaultInterfaceImpl as following.
  1. public static void DoDivide2()  
  2.             {  
  3.                 try  
  4.                 {  
  5.                     int a = 3;  
  6.                     int b = 0;  
  7.                     int c = a / b;  
  8.                 }  
  9.                 catch (DivideByZeroException ex)  
  10.                 {  
  11.                     ILogger2 logger = new CustomLogger2(); // Converting to interface                 
  12.                     logger.Log(ex); // Calling new Log overload  
  13.                 }  
  14.             }  
Upon running the above code, the overridden version of Log method (Inside CustomLogger2) will be called and the message "From: Overridden method: Hello readers of Learning C# 8!!" will be printed.
 
You might have noticed that override keyword has not been added in Log method in CustomLogger2 class and this differentiates with the regular virtual and override.
 

Summary

 
Default interface implementation is the most awaited feature that has been added as part of C# 8. It helps to add default implementation without disturbing existing contracts.
Author
Prakash Tripathi
28 40.8k 6.3m
Next » Switch Expressions And Pattern Matching