A default interface member is a feature introduced in C# 8 which allows an interface to declare a member with a body. Classes which implement the interface are not required to override a default method. Use this tag for questions relating to C# 8's default interface members
Default interface members were introduced in c#-8. They are similar to Java's default-method feature.
An interface member can now be specified with a code body, and if an implementing class or struct does not provide an implementation of that member, no error occurs. Instead, the default implementation is used.
Default interface members help in the following scenarios :
- Interface versioning
- Interoperation with APIs targeting Android (Java) and iOS (Swift), which support similar features.
- To implement traits without requiring multiple inheritance, similar to PHP and Scala traits. Java 8 and later also supports traits through default interface methods
- Code Reuse in structs (Thanks Eirik Tsarpalis!)
Interface Versioning
This example is adapted from Mads Torgersen's article on Default implementations in interfaces:
Let’s say that we offer the following interface:
interface ILogger
{
void Log(LogLevel level, string message);
}
And a class that implements it :
class ConsoleLogger : ILogger
{
public void Log(LogLevel level, string message) { ... }
}
With default members, the interface can be modified without breaking ConsoleLogger
:
interface ILogger
{
void Log(LogLevel level, string message);
void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());
}
The ConsoleLogger still satisfies the contract provided by the interface: if it is converted to the interface and the new Log method is called it will work just fine - the interface’s default implementation is just called:
public static void LogException(ConsoleLogger logger, Exception ex)
{
ILogger ilogger = logger; // Converting to interface
ilogger.Log(ex); // Calling new Log overload
}
An implementing class that does know about the new member is free to implement it in its own way. In that case, the default implementation is just ignored.
Traits
In an imaginary game, items may inherit from a GameItem
class :
public class GameItem { }
Let's assume that a potion doesn't have a location, nor does it move :
public class Potion:GameItem{}
A rock may have a location :
public interface ILocatable
{
public (double x,double y) Location{get;set;}
}
public class Rock:GameItem,ILocatable
{
public (double x,double y) Location{get;set;}
}
A player or monster can also move. Without traits, one possible solution would be to add the ability to move to GameItem
or introduce an intermediate abstract class with that functionality. Modifying GameItem
would also affect Rock
while the abstract class would introduce a relation that probably isn't appropriate.
This can be solved with the IMovable
trait, which can be applied to any type that has a Location
property :
public interface IMovable
{
public abstract (double x,double y) Location{get;set;}
void Move(double angle,double speed)
{
var x=Location.x + speed*Math.Sin(angle);
var y=Location.y + speed*Math.Cos(angle);
Location=(x,y);
}
}
That trait can be applied to any class as long as it has a matching Location
property:
public class Player:GameItem,ILocatable,IMovable
{
public (double x,double y) Location{get;set;}
}
public class Monster:GameItem,ILocatable,IMovable
{
public (double x,double y) Location{get;set;}
}
Trait Example - Reading settings in a container environment
In container-based or serverless applications, one of the most common ways to distribute settings is through environment variables. DIMs can be used to create a trait that retrieves a specific environment variable each time it's called, eg :
interface IGithubSettings
{
public string CurrentToken => Environment.GetEnvironmentVariable("GitHubToken");
}
References :
- Tutorial: Update interfaces with default interface members in C# 8.0
- Default implementations in interfaces by Mads Torgersen
- Wikipedia entry on Traits
- Default Interface Methods proposal including the motivation