Senior 5 min · March 06, 2026

C# Explicit Interface — Silent NotImplementedException

One endpoint threw 500 errors from a hidden NotImplementedException in explicit interface.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • 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
Plain-English First

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.

InterfaceBasics.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;

// The interface — a pure contract describing a capability.
// No fields, no constructor, no implementation (pre-C# 8 style).
public interface IGreeter
{
    // Any class implementing IGreeter MUST provide this method.
    // Access modifiers are implicitly public on interface members.
    string BuildGreeting(string recipientName);

    // Read-only property — implementors must expose a Name.
    string GreeterName { get; }
}

// FormalGreeter honours the contract in one way...
public class FormalGreeter : IGreeter
{
    public string GreeterName => "Corporate Bot";

    public string BuildGreeting(string recipientName)
    {
        // Formal implementation — very stiff.
        return $"Good day, {recipientName}. I trust you are well.";
    }
}

// CasualGreeter honours the same contract in a completely different way.
public class CasualGreeter : IGreeter
{
    public string GreeterName => "Buddy Bot";

    public string BuildGreeting(string recipientName)
    {
        // Casual implementation — same contract, totally different personality.
        return $"Hey {recipientName}! What's up?";
    }
}

public class Program
{
    // Notice: the parameter type is IGreeter, not FormalGreeter or CasualGreeter.
    // This method has NO idea which concrete class it receives — and it doesn't need to.
    static void PrintGreeting(IGreeter greeter, string name)
    {
        Console.WriteLine($"[{greeter.GreeterName}] says: {greeter.BuildGreeting(name)}");
    }

    static void Main()
    {
        IGreeter formal = new FormalGreeter();
        IGreeter casual = new CasualGreeter();

        PrintGreeting(formal, "Alice");  // Passes a FormalGreeter through IGreeter
        PrintGreeting(casual, "Bob");    // Passes a CasualGreeter through the same slot
    }
}
Output
[Corporate Bot] says: Good day, Alice. I trust you are well.
[Buddy Bot] says: Hey Bob! What's up?
Why `IGreeter` not `Greeter`?
The leading 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.
Production Insight
In production, an interface mismatch (wrong parameter type, missing member) surfaces as a compile error, not a runtime surprise. That's the benefit of contracts. But watch out: explicit interface implementations can be invisible to devs examining the class — they only appear when you cast to the interface.
If a method signature changes in the interface, every implementor must be updated. A common production pain is updating a shared library's interface without updating all consumers — results in runtime TypeLoadException.
Key Takeaway
An interface declares a contract — what, not how.
Classes signal capability by implementing interfaces.
You can't instantiate an interface; you always work through a concrete type.

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.

RepositoryPattern.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using System;
using System.Collections.Generic;
using System.Linq;

// --- The Contract ---
// ProductService will ONLY ever know about this interface.
public interface IProductRepository
{
    IEnumerable<Product> GetAllProducts();
    Product? GetById(int productId);
    void AddProduct(Product product);
}

// --- The Domain Model ---
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

// --- Real Implementation (used in production) ---
// Imagine this hits SQL Server. We keep it simple here.
public class InMemoryProductRepository : IProductRepository
{
    // Simulates a database table in memory.
    private readonly List<Product> _products = new()
    {
        new Product { Id = 1, Name = "Mechanical Keyboard", Price = 129.99m },
        new Product { Id = 2, Name = "USB-C Hub",           Price = 49.99m  }
    };

    public IEnumerable<Product> GetAllProducts() => _products;

    public Product? GetById(int productId)
        => _products.FirstOrDefault(p => p.Id == productId);

    public void AddProduct(Product product)
    {
        // Auto-increment ID, just like a real DB sequence would.
        product.Id = _products.Count + 1;
        _products.Add(product);
    }
}

// --- Business Logic Layer ---
// ProductService has NO knowledge of how data is stored.
// It only talks to IProductRepository — could be SQL, Mongo, a flat file, anything.
public class ProductService
{
    private readonly IProductRepository _repository;

    // The repository is INJECTED — ProductService doesn't create it.
    public ProductService(IProductRepository repository)
    {
        _repository = repository;
    }

    public void DisplayAllProducts()
    {
        var products = _repository.GetAllProducts();
        Console.WriteLine("=== Product Catalogue ===");
        foreach (var product in products)
        {
            // Business formatting logic lives here, not in the repository.
            Console.WriteLine($"  [{product.Id}] {product.Name} — ${product.Price:F2}");
        }
    }

    public void AddNewProduct(string name, decimal price)
    {
        var newProduct = new Product { Name = name, Price = price };
        _repository.AddProduct(newProduct);
        Console.WriteLine($"Added: '{name}' with ID {newProduct.Id}");
    }
}

public class Program
{
    static void Main()
    {
        // Wire up the dependency: tell the app which concrete class backs the interface.
        // In ASP.NET Core this line lives in Program.cs / Startup.cs.
        IProductRepository repository = new InMemoryProductRepository();
        var service = new ProductService(repository);

        service.DisplayAllProducts();
        service.AddNewProduct("Wireless Mouse", 35.00m);
        service.DisplayAllProducts();
    }
}
Output
=== Product Catalogue ===
[1] Mechanical Keyboard — $129.99
[2] USB-C Hub — $49.99
Added: 'Wireless Mouse' with ID 3
=== Product Catalogue ===
[1] Mechanical Keyboard — $129.99
[2] USB-C Hub — $49.99
[3] Wireless Mouse — $35.00
Pro Tip: Testing Without a Database
Create a 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.
Production Insight
The Repository pattern only works if your DI registration is correct. A common production failure: registering the concrete class with two different lifetimes (scoped vs transient) for the same interface causes subtle bugs in concurrent requests.
Also, if your FakeRepository is accidentally used in production (e.g., wrong DI config), you lose all data after restart. That's why many teams use a 'real' light database for development and add a health check that verifies the repository type matches expectations.
Key Takeaway
Interface + DI = your business logic stays ignorant of the data source.
Register once, swap anywhere.
Always verify which implementation the container resolves in production config.

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.

ExplicitInterfaceImpl.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System;

// Two interfaces with a conflicting method signature.
public interface ICsvExporter
{
    // Export to CSV format.
    string Export(string filePath);
}

public interface IJsonExporter
{
    // Export to JSON format — same signature, totally different intent.
    string Export(string filePath);
}

public class DataExporter : ICsvExporter, IJsonExporter
{
    // Explicit implementation for ICsvExporter.
    // Note: NO access modifier — explicit implementations are always private by default.
    string ICsvExporter.Export(string filePath)
    {
        return $"Writing CSV data to: {filePath}.csv";
    }

    // Explicit implementation for IJsonExporter.
    string IJsonExporter.Export(string filePath)
    {
        return $"Writing JSON data to: {filePath}.json";
    }

    // You can still have a regular public method on the class itself.
    public string ExportSummary()
    {
        return "DataExporter supports both CSV and JSON formats.";
    }
}

public class Program
{
    static void Main()
    {
        var exporter = new DataExporter();

        // exporter.Export("report") — this would NOT compile.
        // Explicit implementations are only reachable via the interface reference.

        // Cast to the specific interface to get the right behaviour.
        ICsvExporter csvExporter   = exporter;
        IJsonExporter jsonExporter = exporter;

        Console.WriteLine(csvExporter.Export("sales_report"));
        Console.WriteLine(jsonExporter.Export("sales_report"));
        Console.WriteLine(exporter.ExportSummary());
    }
}
Output
Writing CSV data to: sales_report.csv
Writing JSON data to: sales_report.json
DataExporter supports both CSV and JSON formats.
Watch Out: Explicit Members Are Hidden
Explicit interface members have no access modifier and are invisible on the class reference. If a junior dev calls 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.
Production Insight
Explicit interface implementations are a common source of misconceptions in code reviews. Because they're hidden, a developer might add a new interface method to the interface but forget to implement it explicitly — the class compiles fine but throws MissingMethodException at runtime when the method is invoked through the interface.
Another pain point: reflection-based tools (like some serializers or DI containers) might not see explicit members as part of the class, leading to unexpected behavior. Always test by calling methods through the interface reference in integration tests.
Key Takeaway
Explicit implementation = conflict resolution for same-named members.
Hidden from class reference, only visible via interface cast.
Leaves a compile-time safety net that's easy to trip over if you're not careful.

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.

DefaultMethodsAndInheritance.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System;
using System.Collections.Generic;

// Base interface — read-only capability.
public interface IReadableRepository<T>
{
    T? GetById(int id);
    IEnumerable<T> GetAll();

    // Default method added in C# 8 — existing implementors get this for free.
    // They can override it, but they don't HAVE to.
    bool Exists(int id) => GetById(id) != null;  // Default implementation uses GetById
}

// Extended interface — adds write capability on top of read.
public interface IWritableRepository<T> : IReadableRepository<T>
{
    void Add(T item);
    void Delete(int id);
}

public class Note
{
    public int Id { get; set; }
    public string Content { get; set; } = string.Empty;
}

// Implements the full read-write contract.
public class NoteRepository : IWritableRepository<Note>
{
    private readonly List<Note> _notes = new()
    {
        new Note { Id = 1, Content = "Buy groceries" },
        new Note { Id = 2, Content = "Review pull request" }
    };

    public Note? GetById(int id) => _notes.Find(n => n.Id == id);
    public IEnumerable<Note> GetAll() => _notes;

    public void Add(Note note)
    {
        note.Id = _notes.Count + 1;
        _notes.Add(note);
    }

    public void Delete(int id) => _notes.RemoveAll(n => n.Id == id);

    // We're NOT overriding Exists() — we're happy with the default implementation.
}

public class Program
{
    static void Main()
    {
        IWritableRepository<Note> notes = new NoteRepository();

        // Exists() comes from the default interface method — no override needed.
        Console.WriteLine($"Note 1 exists: {notes.Exists(1)}");   // Uses default
        Console.WriteLine($"Note 99 exists: {notes.Exists(99)}"); // Uses default

        notes.Add(new Note { Content = "Ship the feature" });
        notes.Delete(1);

        Console.WriteLine("\nRemaining notes:");
        foreach (var note in notes.GetAll())
        {
            Console.WriteLine($"  [{note.Id}] {note.Content}");
        }
    }
}
Output
Note 1 exists: True
Note 99 exists: False
Remaining notes:
[2] Review pull request
[3] Ship the feature
Interview Gold: Default Methods vs Abstract Classes
Interviewers love asking 'when would you choose an interface with default methods over an abstract class?' The answer: choose interfaces when you need multiple implementation support or are evolving a public library contract. Choose abstract classes when you need shared state, a constructor, or protected members. They solve different problems and are not interchangeable.
Production Insight
Default interface methods sound like a saviour, but they introduce a new failure mode: diamond problem resolution. If two interfaces both define the same default method, the implementing class must explicitly override it or you get a compile error (CS8701). In production, this often happens when you inherit from two library interfaces that both added the same default method in different versions.
Also, default methods are virtual and can be overridden, but the override must use the explicit interface syntax — confusing for many devs. A common bug: the implementor thinks they're overriding the default but actually creates a separate unrelated method.
Key Takeaway
Default methods = interface evolution, not class replacement.
Use them to add non-breaking changes to contracts.
For shared state and constructors, stick with abstract classes.

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.

InterfaceSegregation.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System;
using System.Collections.Generic;

// Bad: Fat interface
public interface IDataManager
{
    void Connect();
    void Disconnect();
    IEnumerable<string> Query(string sql);
    int Insert(string table, Dictionary<string, object> data);
    void Update(string table, int id, Dictionary<string, object> data);
    void Delete(string table, int id);
    void ExportToCsv(string filePath);
    void Backup();
}

// Good: Segregated interfaces
public interface IConnectionManager
{
    void Connect();
    void Disconnect();
}

public interface IReadOnlyRepository
{
    IEnumerable<string> Query(string sql);
}

public interface IWriteOnlyRepository
{
    int Insert(string table, Dictionary<string, object> data);
    void Update(string table, int id, Dictionary<string, object> data);
    void Delete(string table, int id);
}

public interface IDataExporter
{
    void ExportToCsv(string filePath);
}

public interface IDataBackup
{
    void Backup();
}

// A class that only reads data now only implements what it needs
public class ReadOnlyReportService : IReadOnlyRepository
{
    public IEnumerable<string> Query(string sql)
    {
        // Real query logic
        Console.WriteLine($"Executing query: {sql}");
        return new[] { "row1", "row2" };
    }
}

// A full data manager implements all relevant interfaces
public class FullSqlDataManager : IConnectionManager, IReadOnlyRepository, IWriteOnlyRepository, IDataExporter, IDataBackup
{
    public void Connect() => Console.WriteLine("Connected");
    public void Disconnect() => Console.WriteLine("Disconnected");
    public IEnumerable<string> Query(string sql) { Console.WriteLine($"Query: {sql}"); return new[] { "result" }; }
    public int Insert(string table, Dictionary<string, object> data) { Console.WriteLine($"Insert into {table}"); return 1; }
    public void Update(string table, int id, Dictionary<string, object> data) => Console.WriteLine($"Update {table}:{id}");
    public void Delete(string table, int id) => Console.WriteLine($"Delete from {table}:{id}");
    public void ExportToCsv(string filePath) => Console.WriteLine($"Export to {filePath}");
    public void Backup() => Console.WriteLine("Backup completed");
}

public class Program
{
    static void Main()
    {
        // Report service only uses read interface
        IReadOnlyRepository reader = new ReadOnlyReportService();
        var data = reader.Query("SELECT * FROM Products");
        Console.WriteLine(string.Join(", ", data));

        // Admin tool uses full set
        var full = new FullSqlDataManager();
        ((IConnectionManager)full).Connect();
        ((IWriteOnlyRepository)full).Insert("Products", new() { { "Name", "Laptop" } });
    }
}
Output
Executing query: SELECT * FROM Products
row1, row2
Connected
Insert into Products
The Restaurant Waiter Analogy
  • 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.
Production Insight
In a real project, interface segregation prevents 'god classes' that know everything. When a new feature requires a new data operation, you add a new focused interface instead of bloating an existing one. This keeps changes local and reduces the chance of breaking unrelated consumers.
A concrete example: a team added a 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.
Key Takeaway
Keep interfaces small and focused — one capability per interface.
If you're forced to throw 'NotSupportedException', you've violated ISP.
Segregation leads to better testability and fewer hidden dependencies.
When to Split an Interface
IfA client depends on the interface but only uses 2 of its 5 methods
UseSplit the interface — extract the two methods into a smaller role interface
IfMultiple implementors all throw NotImplementedException for the same method
UseRemove the method from the interface or create a separate interface for that capability
IfThe interface has more than 4 members and serves multiple architectural layers
UseReview each member's purpose and segregate by responsibility (read, write, export, etc.)
IfA new feature requires adding a method to an existing interface that many classes implement
UseConsider adding a default method (C# 8+) or creating a new interface that inherits from the original
● Production incidentPOST-MORTEMseverity: high

The Silent NotImplementedException

Symptom
One endpoint consistently threw 500 errors with 'Method not implemented' while all other endpoints worked fine. No recent deployments to that service.
Assumption
The team assumed a database connection issue or a misconfiguration in the DI container.
Root cause
A developer had implemented 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.
Fix
Examine all explicit interface implementations in the class. Add the missing logic or use throw new NotImplementedException() as a deliberate placeholder with a clear comment. In this case, the fix was to implement GetDiscountedProducts properly.
Key lesson
  • 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.
Production debug guideSymptom → Action mapping for common interface misconfigurations4 entries
Symptom · 01
InvalidOperationException: No service for type 'IXxx' has been registered
Fix
Check Program.cs or Startup.cs for missing AddScoped/AddTransient/AddSingleton registration. Ensure the concrete class is registered against the interface.
Symptom · 02
Method throws NotImplementedException when called through interface
Fix
Search for explicit interface implementations in the class. Use dotnet run --project yourproject -- --debug-interface (if you have a diagnostics endpoint) or add a temporary Console.WriteLine to confirm which implementation runs.
Symptom · 03
Mock setup fails in unit test with 'Invalid setup on non-overridable member'
Fix
Ensure the mocked method/property is part of the interface. Moq can only mock interface members or virtual methods on classes. Convert to interface-based design if the class is sealed.
Symptom · 04
Compile error: 'Class does not implement interface member'
Fix
Check the interface signature vs the class method: return type, parameter names (yes, names must match for named arguments, but signature is type-based). Also verify if the member is an event/property and the class implemented it as a method by mistake.
★ Interface Debugging Quick ReferenceCommon interface failures, immediate actions, and fixes
`IServiceProvider.GetService` returns null
Immediate action
Check registration in DI container
Commands
dotnet run --list-interfaces (custom)
Check Startup.cs for AddScoped<IService, Service>()
Fix now
Add the missing registration and restart the app
`System.NotImplementedException` at runtime+
Immediate action
Find the explicit interface implementation
Commands
Search class for 'InterfaceName.' prefix
Use dotnet-counters to see exception rate
Fix now
Implement the method or remove the explicit implementation and add a public method
Unit test mock returns default value unexpectedly+
Immediate action
Verify the setup method name matches interface
Commands
Check Moq setup: mock.Setup(x => x.Method())
Use mock.Verify() to confirm call happened
Fix now
Use strict mock behavior: new Mock<IService>(MockBehavior.Strict)
Interface vs Abstract Class
Feature / AspectInterfaceAbstract Class
Multiple inheritanceYes — a class can implement many interfacesNo — a class can only inherit one abstract class
Instance fields / stateNot allowed (before C# 11)Allowed — can hold shared data
ConstructorsNot allowedAllowed — can enforce initialisation
Default implementationsYes, from C# 8 onwards (use sparingly)Yes — via regular method bodies
Access modifiers on membersAll public by default; no private members pre-C# 8Full range: public, protected, private
Best used forDefining capabilities a type can have (IDisposable, IComparable)Defining a base type with shared logic (Animal → Dog, Cat)
Dependency InjectionIdeal — DI containers bind interfaces to concrete typesPossible but less common
Unit testing / mockingTrivial — Moq, NSubstitute mock interfaces nativelyHarder — requires virtual methods

Key takeaways

1
An interface is a binding contract
it defines WHAT a type can do, never HOW. Any class that signs the contract must deliver every member, but the implementation is entirely its own business.
2
The Repository pattern is the clearest real-world example of interfaces earning their value
ProductService talks only to IProductRepository, making it trivially swappable between SQL, in-memory, and fake test implementations without changing a line of business logic.
3
Explicit interface implementation is the clean solution when two interfaces clash on a method name
the method is hidden from the class reference and only reachable through a cast to the specific interface type.
4
Default interface methods (C# 8+) exist for library evolution, not everyday design. Use them to add new members to a published interface without breaking existing implementors
not as a shortcut to avoid proper abstraction thinking.
5
Interface Segregation Principle
Keep interfaces small and focused. If a class throws NotImplementedException, your interface is too fat.

Common mistakes to avoid

4 patterns
×

Making interfaces too large (Interface Segregation violation)

Symptom
Implementing classes full of throw new NotImplementedException() because they only care about two of the ten interface members.
Fix
Split fat interfaces into focused, single-purpose ones. IProductReader and IProductWriter are better than one giant IProductRepository that forces every implementor to handle both.
×

Adding access modifiers to interface members

Symptom
Compiler error: 'The modifier public is not valid for this item' appears when you write public string GetName(); inside an interface.
Fix
Interface members are implicitly public. Just write 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

Symptom
You define 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.
Fix
Always declare variables and parameters using the interface type, not the concrete type. Rely on DI or factory methods to supply the concrete instance.
×

Using explicit interface implementation without documenting it

Symptom
A developer adds a new public method to the class that accidentally matches an explicit interface method signature, causing confusion. Or another developer tries to call the method on the class reference but gets a compile error.
Fix
Always add a comment explaining why explicit implementation is used. Consider adding a documentation tag or a unit test that exercises the method through the interface reference.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the difference between an interface and an abstract class in C#,...
Q02SENIOR
Can you explain what explicit interface implementation is and give a sce...
Q03JUNIOR
If I have an `IDisposable` interface that only has one method, why not j...
Q04SENIOR
What are default interface methods and when should you use them? What ar...
Q01 of 04SENIOR

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?

ANSWER
Interfaces define a contract without any implementation (pre-C# 8) and support multiple inheritance. Abstract classes can contain shared state and implementation, but a class can only inherit one. Use interfaces for capabilities and DI (e.g., 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.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Can a C# class implement multiple interfaces at the same time?
02
Can an interface have a constructor in C#?
03
What does it mean when people say 'program to an interface, not an implementation'?
04
Are interfaces considered value types or reference types?
05
Can I have static members in an interface?
🔥

That's OOP in C#. Mark it forged?

5 min read · try the examples if you haven't

Previous
Inheritance in C#
3 / 10 · OOP in C#
Next
Polymorphism in C#