Introduction
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)
- Right-click on your project, click Properties, click Build, click Advanced, and then select “C# 8.0 (beta)” in the Language Version.
- In the menu bar, click Tools, click Options, expand Environment, click Preview Features, and then check the box that says “Use Previews of the .NET Core SDK” as follows.
Background and Use-Case
Default Interface Implementation is based on the Traits programming technique, where the reuse of methods between unrelated classes is promoted. This is also available in other programming languages; for example, its presence 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 the implementation or code block. This feature is designed in such a way that concrete classes have no clue about the existence of the 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.
The point to note here is if you don't provide implementation to interface members, it will be treated as a regular interface member, and the classes implementing interface need to provide implementations to all its members.
Implementation
Let's take an example to understand how to work with it.
interface ILogger
{
void Log(LogLevel level, string message);
void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());
}
class CustomLogger : ILogger
{
public void Log(LogLevel level, string message)
{
Console.WriteLine($"{level.ToString()}: Hello readers of C# 8!! {message}");
}
}
class DefaultInterfaceImpl
{
public static void DoDivide()
{
try
{
int a = 3;
int b = 0;
int c = a / b;
}
catch (DivideByZeroException ex)
{
ILogger logger = new CustomLogger(); // Converting to interface
logger.Log(ex); // Calling new Log overload
}
}
}
As you can see in the code above, the default interface member is invoked from the catch block despite the 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 the CustomLogger class as follows.
CustomLogger logger2 = new CustomLogger();
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)" simply because the 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 follows.
interface ILogger2
{
abstract void Log(LogLevel level, string message);
virtual void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());
}
The above code will give compile-time errors in previous versions of C# 8 as follows.
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 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.
class CustomLogger2 : ILogger2
{
public void Log(LogLevel level, string message)
{
Console.WriteLine($"{level.ToString()}: Hello readers of C# 8!! {message}");
}
public void Log(Exception message)
{
Console.WriteLine($"From: Overridden method: Hello readers of C# 8!! {message}");
}
}
And add a new method in an existing class DefaultInterfaceImpl as following.
public static void DoDivide2()
{
try
{
int a = 3;
int b = 0;
int c = a / b;
}
catch (DivideByZeroException ex)
{
ILogger2 logger = new CustomLogger2(); // Converting to interface
logger.Log(ex); // Calling new Log overload
}
}
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.
You can also have a look at the complete code in the attached zip file to go through and execute.
Hope you have liked the article. Please share your comments or suggestions.