How to Implement an Interface Without Making Members Public Using Explicit Interface Implementation
If you've ever implemented an interface, you've probably encountered the requirement that the member you are implementing must be public. For example, you cannot define a property in an interface and then make the setter internal as demonstrated by the following example:
If you attempt to compile the above code, you will receive an error stating that the class does not implement the interface member because it's not public.
There are use cases for implementing interfaces and make the members non-public. Building off the example above, suppose I want all entity objects to have a an Id property that anyone can get, but only internal classes can set.
I'll walk you through a series of code revisions and solve the problem using explicit interface implementation. Explicit implementation essentially hides the class member so that it can only be accessed through the interface. To use this feature, prefix the member you are implementing with the interface name, as demonstrated by the Id property below:
Now the Id property is no longer visible from the Customer class:
To access an explicitly implemented member, cast it to the interface that contains the member (IEntity in this example):
I don't want my consumers to have to cast entity objects to IEntity every time they need to access the Id property, so I'll add a read-only Id property on the class. At first it might feel strange having a member with the same name without using overloading, overriding or hiding (new keyword), but it actually is pretty logical. A class can implement many interfaces, and explicit implementation is the mechanism that handles name clashes when two interfaces define a member with the same name.
Although it may appear we have arrived at the solution, we aren't quite there yet. Explicit implementation hides the member, but it does not restrict access to it; consumers can still cast the object and access the member. To restrict access, we will split non-public members into an interface, and put an appropriate access modifier on the interface. This way consumers cannot cast to that interface and access explicitly implemented members, because they can't access the interface. Let's revisit the example and make some changes. First, the public interface should only contain that which is public, so we'll remove the setter.
Now we'll move the Id property setter into a new interface that is only visible internally:
Next, we'll update the Customer entity to explicitly implement the IEntityIdSetter interface:
For illustrative purposes and to demonstrate why we used interfaces in the first place, we'll also implement an Employee class that implements the same interfaces:
Now any class within the assembly can set the Id property and the interface enables us to operate on different types yet treat them as one. In this example, we don't care if it's an Employee or a Customer, just that it supports setting the Id:
If any class outside the assembly attempts to set the property, the compiler will display an error that the property is read only:
Summary
Sometimes you need to implement an interface and don't want a member to be public. To achieve this, perform the following steps:
- Separate non-public members into an interface
- Set the interface access modifier to internal (you can't use private, protected or protected internal)
- Explicitly implement the non-public interface member(s)
References
Comments
Anonymous
June 13, 2008
Very nicely done. Seems like a useful technique.Anonymous
June 14, 2008
The comment has been removedAnonymous
June 15, 2008
Anders: Your sample is practically identical to the first code in the article, except the setter is removed from the interface. I think the author's technique is important because it shows that the setter can still be included in an interface, so you can use that style of programming, without exposing your setter to the outside or forcing internal code to reference the concrete class. Also, I do agree that auto-properties are the way to go for this scenario, but that really wasn't the point of the article - and the code can be more easily followed by people who haven't been exposed to the 3.x compilers.Anonymous
June 15, 2008
Anders, there's a subtle but important difference in your solution and the author's one. Using your code it's impossible to SET Id through interface. Suppose you also have Employee class that implements IEntity. Using your solution one can't write the NullId function presented in the article.Anonymous
July 02, 2014
That's the best solution to the problem I've found so far!