C# Explicit Interface — Silent NotImplementedException
One endpoint threw 500 errors from a hidden NotImplementedException in explicit interface.
- C# interface = pure contract declaring members without implementation
- Classes implement interfaces — one class can implement many
- Dependency injection and unit testing both rely on interfaces for loose coupling
- Explicit implementation solves naming conflicts when two interfaces clash
- Default interface methods (C# 8+) allow evolving contracts without breaking implementors
- Biggest mistake: making interfaces too large — violates Interface Segregation Principle
Imagine a universal TV remote. It has buttons labelled Power, Volume Up, Volume Down — and any TV brand (Samsung, LG, Sony) must honour those buttons, but each brand handles them internally in its own way. An interface in C# is that remote control contract: it says 'you MUST support these operations' but stays completely silent on HOW. Any class that signs the contract gets to decide its own implementation. That's it — a binding promise, nothing more.
Every non-trivial C# codebase you'll ever work on uses interfaces. They're the backbone of dependency injection frameworks like ASP.NET Core's built-in DI container, they power mocking libraries like Moq, and they're the reason you can swap a database provider without touching a single line of business logic. If you've ever seen ILogger, IEnumerable, or IDisposable in .NET code and wondered why everything starts with 'I', this article is for you.
The core problem interfaces solve is tight coupling. Without them, your OrderService class might directly instantiate a SqlDatabase object. Now your business logic is physically welded to SQL Server. Want to write a unit test? You need a real database. Want to switch to PostgreSQL? You're rewriting code in the wrong layer. Interfaces break that dependency by introducing a middleman contract — your OrderService only ever knows about IDatabase, and it genuinely doesn't care what's behind it.
By the end of this article you'll understand not just the syntax of interfaces but the design thinking behind them. You'll be able to define your own interfaces, implement them across multiple classes, use explicit interface implementation to resolve naming conflicts, and recognise the patterns — like Strategy and Repository — that interfaces make possible. You'll also know the most common mistakes developers make and exactly how to avoid them.
What an Interface Actually Is — and What It Isn't
An interface is a pure contract. It defines a set of members — methods, properties, events, or indexers — that any implementing class or struct must provide. Before C# 8, interfaces contained zero implementation. From C# 8 onwards, default interface methods exist (we'll cover that), but the mental model of 'contract first' still holds.
Here's what makes interfaces different from abstract classes: an interface carries no state. There are no instance fields. It can't hold data. It just describes capability. Think of it as a job description versus an employee. The job description says 'must be able to drive a forklift.' It doesn't drive anything itself.
A class can implement as many interfaces as it wants — that's the escape hatch C# gives you instead of multiple inheritance. A Document class might implement IPrintable, IExportable, and IVersioned simultaneously. That's three separate capability contracts, all in one class.
The naming convention — the leading I — is a .NET standard enforced by StyleCop and every serious team. It's not optional etiquette; it's how developers instantly recognise a contract type from a concrete type at a glance.
I prefix is a .NET convention defined in Microsoft's Framework Design Guidelines. It's not enforced by the compiler, but every serious team and linter expects it. Breaking this convention is a red flag in code reviews — it makes interfaces indistinguishable from abstract classes at a glance.Real-World Interfaces: The Repository Pattern in Plain English
The most practical place to see interfaces earn their pay is the Repository pattern. Your business logic needs to read and write data — but it shouldn't care whether that data lives in SQL Server, an in-memory list, or a JSON file on disk.
By defining an IProductRepository interface, your ProductService class can be written once and tested without a database. During tests you pass in a FakeProductRepository that returns hardcoded data instantly. In production you pass in a SqlProductRepository that hits the real database. Same ProductService, zero changes.
This is also how ASP.NET Core's dependency injection works. You register IProductRepository → SqlProductRepository in Program.cs, and the DI container injects the right implementation wherever the interface is requested. Swap the registration to a different concrete class and your entire app behaves differently without touching business logic.
This design is called the Dependency Inversion Principle — the D in SOLID. High-level modules (business logic) should not depend on low-level modules (database code). Both should depend on abstractions (interfaces). Once this clicks, you'll see why interfaces are everywhere in professional .NET codebases.
FakeProductRepository : IProductRepository that returns hardcoded test data and never touches disk or network. Your unit tests will run in milliseconds, not seconds, and they'll never fail because someone deleted a row in the test database. This is the single biggest practical win interfaces give you.Explicit Interface Implementation — Solving Naming Conflicts Cleanly
Sometimes a class implements two interfaces that both define a member with the same name but different intended behaviours. This is where explicit interface implementation saves the day.
With explicit implementation you prefix the member name with the interface name (IInterfaceName.MethodName). The method becomes inaccessible through the class reference directly — you must cast to the interface to reach it. This keeps the class's public API clean while still honouring both contracts.
A practical scenario: you're building a DataExporter class that implements both ICsvExporter and IJsonExporter. Both interfaces happen to define a Export(string filePath) method. Without explicit implementation, you'd have a single Export method trying to be two things at once. With explicit implementation, each interface gets its own dedicated implementation, and callers using the interface reference get exactly the right behaviour.
This isn't a common pattern day-to-day, but when you need it you really need it — and knowing it exists separates intermediate developers from beginners. It also comes up in legacy COM interop code in .NET, where interface conflicts are common.
myExporter.Export(...) and gets a compile error, they'll be confused. Always leave a code comment explaining WHY explicit implementation is used here — it's not immediately obvious and future-you will thank present-you.MissingMethodException at runtime when the method is invoked through the interface.Default Interface Methods and Interface Inheritance — C# 8+ Power Features
C# 8 introduced default interface methods — a way to add new functionality to an interface without breaking every class that already implements it. This was a huge deal for library authors who couldn't previously add members to a public interface without forcing a breaking change on thousands of implementors.
A default method has a body defined right on the interface. Implementing classes inherit the default behaviour automatically but can override it if they want. It's not inheritance in the classical OOP sense — it's more like a safety net for evolving contracts over time.
Interfaces can also inherit from other interfaces, letting you build layered capability contracts. IReadableRepository might define GetById and GetAll. IWritableRepository might extend it with Add, Update, and Delete. Your full IProductRepository then inherits both — and a read-only caching layer can implement just IReadableRepository without faking out write methods.
Know the limits though: default methods are a library evolution tool, not a replacement for abstract classes. If you need shared state or a constructor, you still want an abstract class. Use default methods sparingly — overusing them muddies the 'pure contract' clarity that makes interfaces valuable in the first place.
Interface Segregation Principle: Designing Focused Interfaces
The Interface Segregation Principle (ISP) is the 'I' in SOLID. It says: no client should be forced to depend on methods it does not use. Translated: keep your interfaces small and focused. A fat interface that promises ten capabilities forces every implementor to provide all ten — even if they only care about three.
Consider a monster IDataManager interface with Connect, Disconnect, Query, Insert, Update, Delete, ExportToCsv, Backup. A read-only reporting service only needs Query, but it must implement Insert, Delete, etc. — likely by throwing NotSupportedException. That's a code smell that violates ISP.
The fix is straightforward: split the fat interface into role-based ones. IConnectionManager, IReadOnlyRepository, IWriteOnlyRepository, IDataExporter, IDataBackup. Then each client depends only on the interface(s) it actually needs. A reporting service consumes IReadOnlyRepository; an admin tool consumes both read and write interfaces.
This leads to more interfaces in your codebase, but that's a feature, not a bug. Each interface captures a single capability, making the system easier to test, mock, and extend.
- A fat interface is like a waiter who also has to cook, clean, and manage bookings.
- Small, focused interfaces let different roles (waiter, chef, cleaner) each implement only what they need.
- If a class implements an interface method by throwing NotImplementedException, that's a violation of ISP — the interface is too fat.
- ISP leads to more interfaces but fewer dependencies — your code becomes more flexible and easier to mock.
GenerateReport method to IUserRepository because it was convenient. Later, the cache layer needed to implement IUserRepository but had to throw for GenerateReport — leading to a production incident when the cache was used for a report generation path.The Silent NotImplementedException
IProductService with an explicit interface member for GetDiscountedProducts. That method was never overridden — it did nothing and returned the default. When the endpoint was added, it invoked the method, which threw NotImplementedException because the class's explicit implementation was incomplete.throw new NotImplementedException() as a deliberate placeholder with a clear comment. In this case, the fix was to implement GetDiscountedProducts properly.- Explicit interface members can hide incomplete implementations — they don't show up in the class's public API.
- Always run integration tests that exercise each interface method through the interface reference, not the class reference.
- Use a custom Roslyn analyzer to flag explicit interface implementations that throw NotImplementedException in non-test code.
Program.cs or Startup.cs for missing AddScoped/AddTransient/AddSingleton registration. Ensure the concrete class is registered against the interface.dotnet run --project yourproject -- --debug-interface (if you have a diagnostics endpoint) or add a temporary Console.WriteLine to confirm which implementation runs.Key takeaways
ProductService talks only to IProductRepository, making it trivially swappable between SQL, in-memory, and fake test implementations without changing a line of business logic.Common mistakes to avoid
4 patternsMaking interfaces too large (Interface Segregation violation)
throw new NotImplementedException() because they only care about two of the ten interface members.IProductReader and IProductWriter are better than one giant IProductRepository that forces every implementor to handle both.Adding access modifiers to interface members
public string GetName(); inside an interface.string GetName(); — no modifier needed. The only exception is C# 8+ default methods, which can have private access modifiers.Depending on concrete types instead of the interface
IEmailSender perfectly, then write SmtpEmailSender sender = new SmtpEmailSender() everywhere instead of IEmailSender sender = new SmtpEmailSender(). Now your code is still tightly coupled to the concrete class and you get none of the testability or swap-ability benefits.Using explicit interface implementation without documenting it
Interview Questions on This Topic
What is the difference between an interface and an abstract class in C#, and how do you decide which one to use in a real project?
IDisposable, IRepository). Use abstract classes for base types with shared logic (e.g., Animal base class with Move() default). The deciding factor: if you need multiple inheritance or pure abstraction, choose interface; if you need shared fields or constructors, choose abstract class.Frequently Asked Questions
That's OOP in C#. Mark it forged?
5 min read · try the examples if you haven't