C# Explicit Interface Implementation: When and How to Use It
c# explicit interface implementation: Learn how C# explicit interface implementation works, when to use it, and how it differs from implicit implementation for cleaner...
When a class implements an interface in C#, it can do so implicitly or explicitly. The difference becomes critical when two interfaces declare the same member, or when you want to hide the implementation from the class's public surface. C# explicit interface implementation gives you precise control over how interface members are exposed, but it also changes how callers access those members. Understanding this behavior is essential for designing APIs that are both safe and maintainable.
The Problem That Explicit Implementation Solves
Consider two interfaces that both declare a method with the same signature:
public interface ILogger { void Log(string message); } public interface IAudit { void Log(string message); }
If a class implements both interfaces implicitly, it must provide a single Log method that satisfies both contracts. That method is then accessible directly on the class instance, and both interface references call the same implementation. In many cases that is fine, but sometimes you need different behavior depending on which interface the caller is using. For example, ILogger.Log might write to a console, while IAudit.Log should write to a persistent audit store. With implicit implementation you cannot distinguish between them; the method body is shared.
Explicit implementation solves this by qualifying the method name with the interface name. Each interface gets its own implementation, and the member is not part of the class's public API. This is the primary reason to choose explicit implementation: it lets you satisfy multiple interface contracts independently without exposing those members on the class itself.
Syntax for Explicit Interface Implementation
The syntax is straightforward. Instead of writing public void Log(string message), you write void ILogger.Log(string message). Notice that you omit the access modifier; explicit interface members are always private in the sense that they cannot be accessed through the class instance. Here is the previous example with explicit implementations:
public class Service : ILogger, IAudit { void ILogger.Log(string message) { Console.WriteLine($"Log: {message}"); } void IAudit.Log(string message) { AuditStore.Write(message); } }
The AuditStore.Write call is illustrative; in practice you would inject a dependency. The key point is that each interface method has its own body. To call these methods, you must cast the instance to the respective interface:
var service = new Service(); ((ILogger)service).Log("Hello"); ((IAudit)service).Log("Hello");
Attempting to call service.Log(...) directly results in a compile-time error because the method is not part of the class's public contract. This is a deliberate design choice, not an accident.
How Explicit Implementation Affects Member Visibility
Explicitly implemented members are not accessible through the class instance. They are only reachable through a reference of the interface type. This has a few consequences:
- You cannot call an explicit member from within the class itself unless you cast
thisto the interface. For example, inside another method ofService, you would need((ILogger)this).Log(...). - The member does not appear in IntelliSense when you have a variable of the class type. This reduces clutter and prevents accidental calls to methods that are only meant for interface consumers.
- You cannot apply access modifiers like
public,private, orprotectedto explicit implementations. The compiler enforces that they are effectively private to the class but accessible via the interface.
This visibility restriction is often exactly what you want when an interface method is an implementation detail. For instance, if you have an IDisposable implementation that should not be called directly by application code, you can implement Dispose explicitly so that it only runs when the object is used as an IDisposable. This prevents accidental calls to Dispose from the class's public API.
When to Use Explicit Implementation
Explicit implementation is not always the right choice. It adds a layer of indirection and makes the class's public surface smaller, which can be beneficial or harmful depending on the context. Use it in these situations:
- Interface conflict resolution: When two interfaces declare the same member with the same signature but you need different behavior for each. Explicit implementation is the only clean way to provide separate implementations.
- Hiding implementation details: When an interface member is not part of the class's core responsibility and should only be invoked through the interface. For example, a repository class that implements
IEnumerable<T>might want to hide theGetEnumeratormethod from its public API because callers should use the repository's own query methods instead. - Avoiding ambiguity: If a class inherits from a base class that already has a method with the same name, explicit implementation can prevent the interface member from being confused with the base class method.
- Enforcing a specific usage pattern: When you want to ensure that callers think in terms of the interface rather than the concrete type. This is common in dependency injection scenarios where the concrete type is rarely used directly.
On the other hand, implicit implementation is simpler and more natural when the interface method is a core part of the class's behavior. For example, if a List<T> implements IList<T>, the Add method is naturally part of the list's public API, so implicit implementation is appropriate.
Common Pitfalls and Misunderstandings
One of the most frequent mistakes is forgetting that explicit members are not accessible from the class instance. Developers new to the pattern often try to call service.Log(...) and get a compile error, then assume the implementation is broken. The fix is to cast to the interface, but the real lesson is to design the class so that callers know which interface they are using.
Another pitfall is attempting to add access modifiers to explicit implementations. The compiler rejects public void ILogger.Log(...). You must omit the access modifier entirely. This is a deliberate language rule that reinforces the idea that explicit members are not part of the class's public contract.
There is also a subtle issue with interface mapping. If a class implements an interface explicitly, the method is not considered when the compiler looks for an implicit implementation. This can lead to unexpected behavior if you mix explicit and implicit implementations for the same interface member. For example, if you have an implicit public void Log(string message) and also an explicit void ILogger.Log(string message), the implicit one is used for ILogger calls, and the explicit one is effectively unreachable through that interface. The compiler allows this, but it is almost always a mistake. Keep your implementation strategy consistent for each interface member.
Explicit Implementation and Interface Default Methods
C# 8 introduced default interface methods, which allow interfaces to provide a default implementation. Explicit implementation interacts with default methods in a specific way. If an interface provides a default method, a class can still implement it explicitly, which overrides the default. If the class does not implement the method at all, the default is used. However, explicit implementation of a default method is rare because the default already provides behavior. The main reason to do it is to change the behavior for a specific class while keeping the member hidden from the class's public API.
For example:
public interface IReporter { void Report(string message) => Console.WriteLine(message); } public class CustomReporter : IReporter { void IReporter.Report(string message) { Console.WriteLine($"Custom: {message}"); } }
Here, CustomReporter overrides the default method explicitly. The method is not callable on a CustomReporter instance directly, only through an IReporter reference. This is a valid pattern, but it adds complexity. In most cases, if you need to override a default method, you can do it implicitly with a public method, unless you specifically want to hide it.
Comparison: Implicit vs Explicit Implementation
The choice between implicit and explicit implementation affects accessibility, maintainability, and how the class is consumed. The following table summarizes the key differences:
| Aspect | Implicit Implementation | Explicit Implementation |
|---|---|---|
| Member visibility | Public on the class | Only via interface reference |
| Access modifier | Required (public) | Not allowed (compiler enforces) |
| Multiple interfaces | Shared implementation for same signature | Separate implementation per interface |
| IntelliSense | Shows on class instance | Hidden from class instance |
| Typical use case | Core class behavior | Conflict resolution or hiding details |
This table is not exhaustive, but it highlights the tradeoffs. Implicit implementation is simpler and more discoverable. Explicit implementation gives you finer control but requires callers to use the interface type.
Maintainability and Runtime Behavior
From a maintainability perspective, explicit implementation can make a class harder to consume if overused. Every explicit member forces callers to cast to an interface, which adds boilerplate and obscures the class's actual capabilities. On the other hand, it can make the class's public API cleaner by removing methods that are only relevant in interface contexts. The key is to use explicit implementation deliberately, not as a default.
At runtime, there is no performance penalty for explicit implementation. The CLR resolves interface method calls through the interface's method table, and whether the implementation is explicit or implicit does not change the dispatch mechanism. The only difference is in how the method is exposed at the metadata level. Therefore, performance is not a reason to choose one over the other.
One operational consideration is debugging. When you have an explicit implementation, the method name in the call stack includes the interface name, such as ILogger.Log. This can be helpful because it immediately tells you which interface contract the method satisfies. However, it can also be confusing if you are not expecting the interface prefix.
Another point is that explicit implementation can help with versioning. If you later add a new interface to a class, you can implement it explicitly without changing the class's existing public API. This is particularly useful in library development where breaking changes are costly. The class's public surface remains stable, and the new interface support is only visible to callers who explicitly use that interface.
Final Technical Consideration: Interface Casting and Nullability
When you cast an object to an interface to call an explicit member, you need to be aware of nullability. If the object might be null, the cast itself is fine, but calling a method on a null reference throws a NullReferenceException. This is no different from any other method call, but it is worth remembering because explicit members are often called in contexts where the object is obtained from a collection or a factory. Always ensure the reference is non-null before invoking the interface method, or use the null-conditional operator if the method can be safely skipped:
ILogger? logger = service as ILogger; logger?.Log("Message");
This pattern is useful when you are not sure whether the object implements the interface. The as operator returns null if the cast fails, and the null-conditional operator avoids the call. This is a common idiom when working with explicit implementations because the member is not visible on the class, so you often need to perform a runtime type check.
Explicit interface implementation is a powerful tool in C#. It solves real design problems, but it also introduces a different calling convention that every developer on the team must understand. By using it selectively and explaining the rationale in code reviews, you can keep your codebase clean and your interfaces honest.