Introduction Last September, Microsoft released a bunch of new features with C# 8.0. One of the major features that they introduced is Default interface methods. In this post, we'll explain what they are, show a few examples and provide guidance to avoid common pitfalls. What are default interface methods in C# 8.0 Interfaces in C# used to be definitions or contracts without any behavior. This assumption is no longer true. Now, with default interface methods, you can define a default body for any interface methods. When you do so, they mostly behave like virtual methods in an abstract class. Why are default interface methods important If they behave like virtual methods on an abstract class, why not simply use an abstract class? There is one big difference that justifies their existence: In C#, you can only inherit from one base class, but you can implement as many interfaces as you want. That subtle point makes all the difference when it comes to code reuse, a.k.a. the . That new feature will enable many scenarios that were previously difficult or impossible to achieve. Don't Repeat Yourself (DRY) principle e.g. Extend interfaces safely by adding methods with implementations Create parameterized implementations to provide greater flexibility Enable implementers to provide a more specific implementation in the form of an override Real examples of default interface methods Without default interface methods { ; ; ; ; } : { { Console.WriteLine( ); } { .WriteCore(LogLevel.Information, message); } { .WriteCore(LogLevel.Warning, message); } { .WriteCore(LogLevel.Error, message); } } : { { (level) { LogLevel.Information: Trace.TraceInformation(message); ; LogLevel.Warning: Trace.TraceWarning(message); ; LogLevel.Error: Trace.TraceError(message); ; } } { .WriteCore(LogLevel.Information, message); } { .WriteCore(LogLevel.Warning, message); } { .WriteCore(LogLevel.Error, message); } } internal interface ILogger ( ) void WriteCore LogLevel level, message string ( ) void WriteInformation message string ( ) void WriteWarning message string ( ) void WriteError message string internal class ConsoleLogger ILogger ( ) public void WriteCore LogLevel level, message string $" : " {level} {message} ( ) public void WriteInformation message string this ( ) public void WriteWarning message string this ( ) public void WriteError message string this internal class TraceLogger ILogger ( ) public void WriteCore LogLevel level, message string switch case break case break case break ( ) public void WriteInformation message string this ( ) public void WriteWarning message string this ( ) public void WriteError message string this For every implementation of `ILogger` we have to repeat this same code block: { .WriteCore(LogLevel.Information, message); } { .WriteCore(LogLevel.Warning, message); } { .WriteCore(LogLevel.Error, message); } ( ) public void WriteInformation message string this ( ) public void WriteWarning message string this ( ) public void WriteError message string this With default interface methods { ; { .WriteCore(LogLevel.Information, message); } { .WriteCore(LogLevel.Warning, message); } { .WriteCore(LogLevel.Error, message); } } : { { Console.WriteLine( ); } } : { { (level) { LogLevel.Information: Trace.TraceInformation(message); ; LogLevel.Warning: Trace.TraceWarning(message); ; LogLevel.Error: Trace.TraceError(message); ; } } } internal interface ILogger ( ) void WriteCore LogLevel level, message string ( ) void WriteInformation message string this ( ) void WriteWarning message string this ( ) void WriteError message string this internal class ConsoleLogger ILogger ( ) public void WriteCore LogLevel level, message string $" : " {level} {message} internal class TraceLogger ILogger ( ) public void WriteCore LogLevel level, message string switch case break case break case break Haaaaa... this is much better. Now the implementations only contain the relevant code. Pitfall to avoid As much as multiple inheritance is awesome, you also need to be very mindful of the dreaded . diamond problem It's an ambiguity that arises when two classes B and C inherit from A, and class D inherits from both B and C. If there is a method in A that B and C have overridden, and D does not override it, then which version of the method does D inherit: that of B, or that of C? Closing There, you have another great tool to put in your toolbox to keep your code clean and maintainable. This approach is also very non-intrusive and won't conflict with existing code, which means it will be easy to slowly adopt it. If you want to hear more on this subject, let me know in the comments. References Microsoft documentation Tutorial diamond problem All code samples are available on github