One of the fun things about defining objects and their APIs in C# is thinking about which bits you want your consumers to see, and which bits need to be there just for you, and consumers should avoid using. Most developers are aware of keywords like
public
/
private
and
internal
for achieving this, but Explicit Interfaces are another approach to this...
An interface is a way of expressing a contract you want classes to implement, but without giving any implementation. For example, a simple example might look like:
public interface IExample { void Start(); void Stop(); int Status(); }
And a class can implement that contract:
public class ExampleClass : IExample { public void Start() { // code } public void Stop() { // code } public int Status() { // code } }
So your code can work with these methods directly on an object:
var obj = new ExampleClass(); obj.Start(); // do stuff obj.Stop();
Or your can access them by their interface instead:
public void DoStuff(IExample ex) { ex.Start(); // do stuff ex.Stop(); }
C# also has the concept of Explicit Interfaces - which are documented as being used to disambiguate if you find yourself implementing two interfaces on the same object where they both specify a method with the same signature. If you want to have two implementations for this method on the same class you can have one public one and one explicit one - where you have to cast the object to the interface type to call it.
Sometimes, however, you need a class to implement an interface for the internals of your app, and you don't really want to confuse users with it by making the implementation of it visible. You can't use access modifier keywords for this - interface members declared in your classes only allow
public
. So (perhaps abusing the compiler a bit?) you can use Explicit Interfaces achieve this. Basically this means you declare these members with their interface name rather than an access modifier and they are only visible when you've cast your object to the interface itself. For example:
public class ExampleClass : IExample { void IExample.Start() { // code } void IExample.Stop() { // code } int IExample.Status() { // code } }
So now this code won't compile:
var obj = new ExampleClass(); obj.Start(); // do stuff obj.Stop();
Because
obj
doesn't expose a public
Start()
or
Stop()
method any more.
But this still works:
public void DoStuff(IExample ex) { ex.Start(); // do stuff ex.Stop(); }
Because the
IExample
interface does expose them, even if they're not public on the object any more.
So consumers of your
ExampleClass
won't see these methods in autocomplete, unless that object is explicitly cast to the interface type.
What I'd not realised until recently, is that it works to have only parts of your interface implemented explicitly. Declaring this compiles and works:
public class ExampleClass : IExample { public void Start() { // code } public void Stop() { // code } int IExample.Status() { // code } }
You've met the requirements of the interface because all the methods have a declaration. But normal users of an
ExampleClass
object can't see the
Status()
method because it is not declared as
public
.
That's a good question - I'm still thinking about that. It's certainly not using Explicit Interfaces in the documented manner.
But I did come up with a scenario where this appears useful - even if it is a bit obscure:
Say you have a variety of types of "entity" in your system that all have an
Id
which needs to be assigned in a controlled manner. And you need to have type-specific entity stores which manage this creation process, while hiding where the
Id
s came from. Broadly this sounds like a use case for generics?
So you might have some sort of base entity like:
public abstract class Entity { public int Id { get; } }
and a store that looks something like:
public class EntityStore<T> where T : Entity, new() { public T Fetch(int id) { // some code } public T New() { // some code } }
Consumers of this code can derive something concrete from
Entity
and use an
EntityStore
to manage instances of them.
But how does the store set the Id on new entities in a way that stops consumers of these types changing it later? The two obvious answers would be "in the constructor" or "via the Id property" but neither of those seems great.
The property approach is pretty easy - just add a setter to the
Id
property. But making that setter invisible to normal consumers is tricky. Depending on the structure of your code you may not be able to use
internal
as that's not visible outside of the current DLL. And
protected
can't work because the store doesn't derive from the
Entity
type.
And sort of similarly, constructors are tricky. They're also difficult to hide, and you can only write constraints on the generic type
T
for a parameterless constructor. So you can't make a store which can only be used with entities that expose the right constructor. (Well - without factory methods - but that seems like a more complex approach)
But maybe that explicit interfaces idea does work here?
If we have an interface for "this object has a controlled Id" like so:
public interface IIdentifiable { int Id { get; } void SetId(int id); }
Then you can have an entity which uses a partial-explicit implementation:
public abstract class Entity, IIdentifiable { private int _id = -1; public int Id => _id; IIdentifiable.SetId(int id) { _id = id; } }
And the store can make use of knowing an Entity has to provide this interface:
public class EntityStore<T> where T : Entity, new() { public T Fetch(int id) { // some code } public T New() { var id = SomeCodeToGenerateTheId(); var e = new Entity(); (e as IIdentifiable).SetId(id); return e; } }
So now users can create new types derived from
Entity
and manage them using the generic store. But the autocomplete for their objects won't show them the
SetId()
method when they're working. This doesn't prevent consumer code casting an entity to the interface to get around this, but there's very little visibility protection that can't be defeated somehow - often via Reflection.
So it does look like it's an approach with some uses, even if they're not things you might use often...
↑ Back to top