You can add default method implementations (and constants) to interfaces (in C# 7+), like this:
public interface ISortingAlgorithm
{
public int[] Sort(int[] unsorted);
public void Log(string msg)
{
Console.WriteLine($"{DateTime.Now:hh:mm:ss.ffff} {msg}");
}
}
class Program
{
static void Main(string[] args)
{
ISortingAlgorithm sorter = new PracticalSort();
sorter.Log("I'm going to sort");
var sorted = sorter.Sort(new int[] { 3, 1, 2 });
}
}
Code language: C# (cs)
Note: The Log() method is converting the DateTime to a string with a custom format and including it with the logging message.
This outputs the following:
09:23:42.4334 I'm going to sort
09:23:42.4509 I'm sorting!
Code language: plaintext (plaintext)
Microsoft’s stated purpose for this feature is that it allows you add methods to an interface without breaking existing implementing classes. That’s nice, but it weakens the concept of interfaces (forcing implementers to adhere to a public contract). I wouldn’t use this feature for its official purpose.
OOP purists probably won’t like this feature, but pragmatic programmers will see the practical benefit: it solves one of the key problems of using interfaces by allowing you to easily add common functionality to all implementing classes. Before, you’d have to either use an abstract base class instead of an interface, or add a static method somewhere.
In this article, I’ll show how to use this new feature and point out some of the quirks.
Table of Contents
Without this feature you’d have to use an abstract base class or static methods
Default methods and constants in interfaces solve a key practical problem by allowing you to add common functionality to all implementers. To see the practical benefit of this, you have to look at the alternative ways of solving this problem. Before this feature, you basically had two choices: use an abstract base class instead or put a static method somewhere.
- Using an abstract base class.
Here’s the abstract base class equivalent:
public abstract class SortingAlgorithmBase
{
public abstract int[] Sort(int[] unsorted);
public const string DATE_FORMAT = "hh:mm:ss.ffff";
public virtual void Log(string msg)
{
Console.WriteLine($"{DateTime.Now.ToString(DATE_FORMAT)} {msg}");
}
}
public class PracticalSort : SortingAlgorithmBase
{
public override int[] Sort(int[] unsorted)
{
Log("I'm sorting!");
Array.Sort(unsorted);
return unsorted;
}
}
Code language: C# (cs)
First, this is quite a bit more verbose than using an interface. Second, there’s a principle known as Composition over Inheritance – this states that we should try to avoid using inheritance. When you use default methods with interfaces, technically you’re not using inheritance (the default method is not inherited – more on this later).
- Using a static method.
Here’s the static method equivalent:
public interface ISortingAlgorithm
{
public int[] Sort(int[] unsorted);
}
public static class Utils
{
public const string DATE_FORMAT = "hh:mm:ss.ffff";
public static void Log(string msg)
{
Console.WriteLine($"{DateTime.Now.ToString(DATE_FORMAT)} {msg}");
}
}
public class PracticalSort : ISortingAlgorithm
{
public int[] Sort(int[] unsorted)
{
Utils.Log("I'm sorting!");
Array.Sort(unsorted);
return unsorted;
}
}
Code language: C# (cs)
This is even messier than using an abstract base class. Now you three entities involved – an interface, an implementing class, and a static class. The default method feature is syntax sugar that allows us to avoid this unnecessary cluttering of the codebase.
Override the default method
Interface doesn’t force implementers to implement the method (which is why the stated purpose of this feature isn’t great). They have the option of implementing it. You won’t get an compile-time errors complaining that you haven’t implemented this new method.
Here’s how to override the default method:
public class PracticalSort : ISortingAlgorithm
{
public int[] Sort(int[] unsorted)
{
Log("I'm sorting!");
Array.Sort(unsorted);
return unsorted;
}
public void Log(string msg)
{
//logging without the date
Console.WriteLine(msg);
}
}
Code language: C# (cs)
Now when I run this code, it outputs the following messages:
I'm going to sort
I'm sorting!
Code language: plaintext (plaintext)
Notice that it’s not putting the datetime. This is because it’s using the Log() method defined in PracticalSort instead of the one in the interface.
Call the default method from the implementing class
Default methods aren’t inherited. If this were an abstract base class, you could just call Log(). Instead, to call the method from the implementing class, you have to cast this to the interface type, like this:
public class PracticalSort : ISortingAlgorithm
{
private ISortingAlgorithm me => this;
public int[] Sort(int[] unsorted)
{
me.Log("I'm sorting!");
Array.Sort(unsorted);
return unsorted;
}
}
Code language: C# (cs)
Note: I added the “me” property to avoid verbose casting syntax cluttering the code.
Can’t have default properties in interfaces
This new feature doesn’t apply to properties. Why not? Because you can’t declare instance fields in an interface. You get this compile-time error:
CS0525 Interfaces cannot contain instance fields
Behind the scenes, properties are syntax sugar. They’re getter/setter methods with a backing instance field, like this:
private SortDirections _sortDirection = SortDirections.Asc;
public SortDirections SortDirection
{
get
{
return _sortDirection;
}
set
{
_sortDirection = value;
}
}
Code language: C# (cs)
Therefore, there is no such thing as a default property implementation in an interface.
If you don’t have the property implemented in the class, you’ll get the following compile-time error:
CS0535 ‘PracticalSort’ does not implement interface member ‘ISortingAlgorithm.SortDirections’
Long-story short, you still have to declare the property in both the interface and the implementing class:
public interface ISortingAlgorithm
{
public SortDirections SortDirections { get; set; }
}
public class PracticalSort : ISortingAlgorithm
{
public SortDirections SortDirections { get; set; }
}
Code language: C# (cs)
Constants and static readonly fields on an interface
In addition to default method implementations, you can declare constants and static readonly fields in interfaces.
First, here’s an interface with a constant:
public interface ISortingAlgorithm
{
public int[] Sort(int[] unsorted);
public void Log(string msg)
{
Console.WriteLine($"{DateTime.Now.ToString(DATE_FORMAT)} {msg}");
}
public void LogError(string error)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"{DateTime.Now.ToString(DATE_FORMAT)} {error}");
Console.ResetColor();
}
public const string DATE_FORMAT = "hh:mm:ss.ffff";
}
Code language: C# (cs)
One of the main benefits of having a constant on an interface is that you can use it in default methods in the interface. You can also access the constant from outside the interface like this:
public class PracticalSort : ISortingAlgorithm
{
private ISortingAlgorithm me => (ISortingAlgorithm)this;
public int[] Sort(int[] unsorted)
{
me.Log($"Date format = {ISortingAlgorithm.DATE_FORMAT}");
Array.Sort(unsorted);
return unsorted;
}
}
Code language: C# (cs)
You can also have static readonly fields, like this:
public interface ISortingAlgorithm
{
public int[] Sort(int[] unsorted);
public void Log(string msg)
{
Console.WriteLine($"{DateTime.Now.ToString(DATE_FORMAT)} version={API_VERSION} {msg}");
}
public const string DATE_FORMAT = "hh:mm:ss.ffff";
private static readonly decimal API_VERSION = 2.1m;
}
Code language: C# (cs)
Note: You use ‘static readonly’ when you have a “constant” reference type.