Default interface methods of C# 8 and extension methods have in common that you can "add" a method with implementation to an interface.
"Add" being in quotes here because for extension methods that's not quite precise. Extension methods only associate a method with a type, which makes autocomplete suggest it when you are using a type that has extension methods.
In a framework/library, we have several public APIs where we have an interface and accompanying extension methods within the same assembly and namespace, for example something like this:
namespace SomeFramework.DataTree;
public interface ITreeNode
{
IEnumerable<TreeNode> Children { get; }
}
public static TreeNodeExtensions
{
public static IEnumerable<ITreeNode> GetChildrenRecursive(this ITreeNode treeNode)
{
// traverse the children recursively using ITreeNode.Children
}
}
But in modern .NET (6 or later) is there any reason to favor extension methods over default interface methods in a public API of a library or framework when the extension methods are in the same assembly and namespace as the extended interface?
With default interface methods the above example would look like this:
namespace SomeFramework.DataTree;
public interface ITreeNode
{
IEnumerable<TreeNode> Children { get; }
IEnumerable<ITreeNode> ChildrenRecursive
{
get
{
// traverse the children recursively using ITreeNode.Children
}
}
}
So far I can mainly see disadvantages of using extension methods in the above scenario:
- There still can be cases where the extension methods will not show up in autocompletion because the namespace of the extended interface has not been added (
using SomeFramework.DataTree;
is missing). That can happen, even if you currently have access to an instance of the interface. - It is not possible to override or mock the extension method for example in a unit test. If you use default interface methods this is possible.
- There are no extension properties.
But also can I think of some reason to not use default interface implementations in the above scenario, but I feel they are somewhat esoteric. Or are they not?
- A lot of additional methods that are more like helper methods may clutter the interface and makes the actual abstraction less apparent.
- Default interface methods have the downside that they are invisible if you do not explicitly cast to that interface, as demonstrated here.