Default Implementations Coming to Interfaces With C# 8
Those who are writing libraries and components for public use may find default implementations especially useful as they let us avoid breaking changes in interfaces.
Join the DZone community and get the full member experience.
Join For FreeC# 8.0 will introduce new language feature — default implementations of interface members. This means that we can define a body-to-interface member and implementing class that doesn't implement the given interface member and will use the default one from interface itself. Here's my deep-dive and analysis of default implementions of interfaces.
Default Implementations of Interface Members
Let's start with a classic example based on Mads Torgersen's blog post, Default implementations in interfaces, and take a look at this famous logger example. Let there be an interface for a logger.
public interface ILogger
{
void Info(string message);
void Error(string message);
}
When building logger classes we have to implement all members defined by the interface.
public class ConsoleLogger : ILogger
{
public void Error(string message)
{
Console.WriteLine(message);
}
public void Info(string message)
{
Console.WriteLine(message);
}
}
Suppose we have to extend the ILogger interface with a new member and we don't want to affect any existing classes with this change. This is how we define the interface with the current version of C# (and we get stuck).
public interface ILogger
{
void Info(string message);
void Error(string message);
// New method
void Warn(string message);
}
In C# 8.0, we can solve the problem by providing the implementation to the Warn()
method.
public interface ILogger
{
void Info(string message);
void Error(string message);
// New method
void Warn(string message)
{
Debug.WriteLine(message);
}
}
It builds without errors. Let's try this code out with a simple test.
So, the interface method works as expected and the ConsoleLogger
class is not affected by the change in the interface.
If we implement the Warn()
method in the ConsoleLogger
class then the Warn()
method of class is used.
What Happens Internally?
Every new feature in the C# language is always worth investigating beyond the compiler. Some language features are illusions — for example, how the compiler turns these into typically simple code like the feature never existed. Take a look at my writing about throw expressions in C# 7.0.
To see what the compiler produced I make a default implementation of the Warn()
method and use the Console instead of Debug, as, otherwise, we would get an empty method body when building the code in Release mode. Another possible source of differences may come in from the fact that we have a method with a body in our interface. Let's define also the classic ILogger interface to compare them.
public interface ILoggerClassic
{
void Info(string message);
void Error(string message);
void Warn(string message);
}
After compiling the code and investigating the result in the disassembler we can see the difference in the Intermediate Language level.
The interesting thing is that interface method with the body is missing an abstract keyword. From every other aspect it is defined exactly the same way as the classic interface method.
This fact makes me ask one thing — hasn't this feature been available at the IL level for a long time? Is it possible it will make its way to C# years later? As surprising as it may be, the answer is yes. Years ago when I blogged about how to throw and catch strings in IL I went through the IL book by Vijay Mukhi. Based on this book and Expert .NET 2.0 IL Assembler by Serge Lidin I was able to build a program in IL where the interface method had a body. Of course, it wasn't possible to use it in C# directly but it was possible through reflection. The book by Serge Lidin details extremely well the internals of the .NET runtime and it was kind of horrible for me to see how many responsibilities are actually set to language compilers. So, yes, this feature has existed at the IL level for years.
Do We Really Need Default Implementations?
The need for default implementations of interface members is a similarly hot topic for argument like local functions in C# 7.0. I mean we have survived seven versions of C# without default implementations. What's the point of default implementations, then?
Some developers may feel especially bad about default implementations because technically they are correct and maybe even convenient but they distort planned functionality of implementing classes. The sample code above illustrates the problem well. The signature of the interface is changed and with this all the implementing classes suddenly have some new functionalities injected. We can write a debug logger and make the Warn()
method of ILogger use the Console as an output. But what has a debug logger got to do with the Console? And, even worse, what if the default implementation conflicts with something in the implementing class? Another question — why not abstract the class with default implementations?
Default implementations may actually turn out to be very useful. Suppose we have a public library used by other developers all around the world. We want to release a new version where we have improved some interfaces and, of course, we want to avoid breaking changes. As we have been on interfaces with our library for who knows how long we cannot just jump between interfaces and abstract classes as we wish. If we add new members to our interfaces then we can define implementations so programs using previous versions of our libraries can use also new versions without changes to their codebase. I guess for many developers it makes life way easier.
I did an experiment. I wrote a program, one library with the ILogger interface and another with the ConsoleLogger class. I built my program and published it to other disks on my machine. I ran the application to see if it worked. Everything was fine. Then I added a new Warn()
method with default implementation to ILogger. I build only the ILogger library and replaced it in the folder where I published my program. I ran my program again and it still worked although the interface was changed.
Wrapping Up
Default implementations area a powerful language feature coming to C# 8.0. Although it may seem dangerous for some developers, others will certainly be happy with it. Those who are writing libraries and components for public use may find default implementations especially useful as they let us avoid breaking changes in interfaces.
Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments