TL;DR: Implement interfaces explicitly to convey intent, get more help from the compiler and maybe type less.
Explicit interface implementation is an overlooked language feature
How often do you see or write explicit interface implementations? If your experience is similar to mine, virtually never outside of the code Visual Studio generates for implementing IEnumerable<T>
and the like.
Why not?
Sure, the language feature may originally stem from a need to implement multiple conflicting interfaces on the same class, such as IEnumerable
and IEnumerable<T>
. But it can be used for great benefit outside of that scenario on your daily-life interface implementations.
Let’s consider this example interface method and two classes implementing it implicitly and explicitly, respectively:
interface IFoo { string GetInfo(string id); } class ImplicitImplementation : IFoo { public string GetInfo(string id) => "foo info"; } class ExplicitImplementation : IFoo { string IFoo.GetInfo(string id) => "foo info"; }
The amount of typing is about the same. We do not have to type public
, but we do need to type IFoo
on every method implementation. The only noticable difference is in how the instances can be used. Whereas GetInfo
can be called on an instance of static type ImplicitImplementation
, it cannot be called on an instance of static type ExplicitImplementation
, but only on a reference to that instance with static type IFoo
.
Explicit implementation makes the compiler work for you
What this means is that by implementing the interface explicitly, the compiler will force you to program against the interface. This is A Good ThingTM. It forces you to separate implementation from contract and to declare complete contracts.
Now, imagine the code evolving: methods are added, updated and removed from IFoo
, helper methods are added and removed and their access modifiers changed maybe on the implementing classes.
When a method is added to IFoo
, the compiler will tell you that it needs to be added to the implementing classes. This is true for both implicit and explicit implementations. But if a method is removed from the interface, the compiler will only complain about the explicit implementation, not the implicit implementation:
Without compiler assistance, it is easy to forget to delete the method on all the implementing classes. You now have zombie code. And if it is unit-tested, it is even still referenced.
In addition, there may be code calling the method on an instance that is of the implementation type (not the interface type). This too will go unnoticed. You now have code relying on a method that is no longer part of the actual contract.
Only by having implemented the interface explicitly, the compiler will help you every step of the way: removing the method from implementing classes, removing their tests and detecting all the code that used it.
This is especially helpful for all kinds of decorators and adapters. I try to always use explicit implementation on my decorators and adapters.
OK, we established that explicit implementation increases productivity by making better use of the compiler.
But it can in addition save you a lot of keystrokes, too.
Explicit implementation can enable you to type less
In the first example above, character count was about the same for implicit and explicit implementation. We now look at two cases in which explicit implementation will actually save keystrokes.
The first case comes with optional parameters and their default values.
No need to redeclare default values of optional parameters
An implicit implementation can override a default value with a different value. The actual value that is used when none is supplied in the call now depends on the static type of the instance on which the method is called. If the method is called on an instance of static type ImplicitImplementation
, a default of 23 is used. If the method is called on an instance of static type IFoo
, a default of 42 is used:
This is error-prone. Just omitting the default value in the implementing method is not a solution either, since now the caller has to always provide the non-optional parameter and cannot simply reference the default from the interface.
Effectively, to avoid surprises, the implicitly implementing classes have to redeclare the interface’s default values. This can be an annoying amount of boilerplate typing. The compiler will also not assist you should the interface’s default value change.
This problem does not arise with explicit implementations, because you can only ever call the method on the interface. You do not have to repeat default values for optional parameters in the method signatures of explicit implementations. In fact, the compiler will even warn you that what you are doing makes no sense:
No need to redeclare type constraints
As programming in C# shifts to more functional approaches, the type system is used more and in more complex ways. This also leads to an increase in the use of generic types and type constraints. This is where explicit interface declarations can save you a lot of typing and make method signatures more readable.
Take as an example an interface for a typed RPC-like channel. Note the way the type system is used to ensure that a request type always has a corresponding response type.
abstract class Request<TRequest, TResponse> where TRequest : Request<TRequest, TResponse> where TResponse : Response<TRequest, TResponse> { } abstract class Response<TRequest, TResponse> where TRequest : Request<TRequest, TResponse> where TResponse : Response<TRequest, TResponse> { } class FooRequest : Request<FooRequest, FooResponse> { } class FooResponse : Response<FooRequest, FooResponse> { } interface IChannel { TResponse GetResponse<TRequest, TResponse>(TRequest request) where TRequest : Request<TRequest, TResponse> where TResponse : Response<TRequest, TResponse>; }
An implicit implementation of such methods has to repeat all the type constraints. An explicit implementation does not:
class ImplicitImplementation : IChannel { public TResponse GetResponse<TRequest, TResponse>(TRequest request) where TRequest : Request<TRequest, TResponse> where TResponse : Response<TRequest, TResponse> // that is a lot of typing (pun intended) { throw new NotImplementedException(); } } class ExplicitImplementation : IChannel { TResponse IChannel.GetResponse<TRequest, TResponse>(TRequest request) // nothing to type here (see above) { throw new NotImplementedException(); } }
Imagine having a couple of those methods on the interface and then implementing a handful of decorators (plural). Very annoying to repeat those type constraints over and over again. And then have some more fun when the constraints are subsequently changed on the interface.
And it makes sense, too. The compiler forces you to duplicate the type constraints because you did not tell it that you mean to implement an interface method.
Explicit implementation does not suffer from this and implementations become much more readable without the cruft.
Explicit implementation conveys intent
The more I think about it, the more the default implicit implementation feels a bit duck-typey:
Hey look, I found this class and it happens to have a method with the same signature as this method on this interface. I can slap an interface declaration on it so the compiler lets me treat it as one of its implementations. Is the signature the same on purpose? I don’t know, because the code does not say. Maybe it is just coincidence.
This feeling is reinforced by the type constraint example. The solution: tell the compiler and future readers what you intend to do! Do you want to implement an interface method? Say so. There is an entire language construct for this: explicit interface implementation.
I rest my case.