Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Generic Interface: Why Is Default Implementation Ignored?

Struggling with default implementations in generic interfaces? Learn when and why they’re ignored and how to fix method hiding in C#.
Confused developer looking at C# interface diagram with default implementation being ignored, source code errors in background, and 'Default Ignored?' text overlay Confused developer looking at C# interface diagram with default implementation being ignored, source code errors in background, and 'Default Ignored?' text overlay
  • ⚠️ Default implementations in generic interfaces can silently fail because of method hiding or dispatch problems.
  • 🧠 Default interface implementations are not handled like standard v-tables for class virtual methods. This changes how they act when code runs.
  • 🧰 Interface method behavior changes based on if you call it with an interface type or a concrete class type.
  • 🚫 Generic constraints and explicit implementations can cause default definitions to be ignored.
  • ✅ Avoiding the new keyword and using interface casting makes method calls more dependable.

Introduction

C# 8.0 changed how developers use interfaces. It let them add methods with logic directly into an interface. This feature offers good design choices and helps with backward compatibility. But it also creates hidden problems that are easy to miss. These problems get worse with generic interfaces. This is because how code runs can change a lot. It depends on how you use the interface member, apply rules, or set up your methods. Here, we look closely at how default interface methods act in C#, especially with generics. We will see how they work, when they do not work, and how to stay away from common issues.


Understanding Generic Interfaces

Generic interfaces are a key feature in C# that let developers set up rules for type-safe actions across different kinds of data. This means you can reuse code and build strong, type-checked systems. For example, think about a common repository pattern in business software:

public interface IRepository<T>
{
    void Add(T item);
    T Find(int id);
}

The IRepository<T> interface can be implemented for any entity:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

public class CustomerRepository : IRepository<Customer>
{
    public void Add(Customer item) { /* implementation */ }
    public Customer Find(int id) { /* implementation */ }
}

Or even more generically:

public class MemoryRepository<T> : IRepository<T>
{
    private Dictionary<int, T> _store = new();

    public void Add(T item) { _store[GetId(item)] = item; }
    public T Find(int id) => _store.ContainsKey(id) ? _store[id] : default;
    
    private int GetId(T item) { /* logic here */ return 0; }
}

Generic interfaces allow developers to use abstract ideas but keep options open for the data types they work with. However, this idea gets harder when C# interfaces begin to have actual code inside them, through default implementations. This brings us to the next big feature.


What Are Default Implementations in C# Interfaces?

Default implementation—introduced in C# 8.0 and supported in .NET Core 3.0 or later—means an interface can hold its own method code. This lets interface creators set up default actions for methods. Classes using the interface do not have to change these default methods.

Here's a clean example:

public interface ILogger
{
    void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}

Default implementations are useful for a few reasons:

  • Backward compatibility: You can add new members to existing interfaces without breaking old implementations.
  • Flexibility: Base logic can be provided, much like base classes.
  • Code reduction: Avoids boilerplate in each implementation.

This feature makes interfaces and abstract classes seem more alike. It adds more power, but sadly, also more chances for mistakes.

Learn more: Default Implementations in C# Interfaces – Microsoft Docs


Why Might Default Implementations Be Ignored?

You might expect that defining a method in the interface guarantees it will run when a class omits its own implementation. However, in the case of generic interfaces, this often does not happen.

Here's a counterintuitive example:

public interface IProcessor<T>
{
    void Process(T item)
    {
        Console.WriteLine("Default processing");
    }
}

public class CustomProcessor : IProcessor<string>
{
    // No override here
}

When calling:

var processor = new CustomProcessor();
processor.Process("test");

The compiler might not allow this call, or nothing might happen. Why?

This depends on the object's type. It also depends on if C# can link the method call to the interface's default method when the code runs. Other problems like method hiding or how you cast types can make this worse.


Method Hiding vs. Overriding: Know the Difference

Understanding method hiding is very important when dealing with default interface implementations. C# lets methods in child classes or interfaces either replace (if virtual) or hide (if new) methods with the same name.

Take this intentionally confusing example:

public class CustomProcessor : IProcessor<string>
{
    public new void Process(string item)
    {
        Console.WriteLine("Custom processing");
    }
}

What's happening here?

  • new keyword: Hides the interface's Process<T> method.
  • Accessing Process via CustomProcessor calls the new method.
  • Accessing Process via IProcessor<string> might still call the interface’s default method if the compiler or runtime allows it.

But it is important: hiding does not mean replacing. This can confuse how methods are called. And it breaks the Liskov Substitution Principle if you are not careful.

More insight: Eric Lippert on method hiding


The Role of Explicit Interface Implementation

Explicit interface implementation is another problem area with default implementations. If you use explicit methods, the method only runs if you cast your variable to the interface type.

public class CustomProcessor2 : IProcessor<string>
{
    void IProcessor<string>.Process(string item)
    {
        Console.WriteLine("Explicit interface process");
    }
}

In action:

var c = new CustomProcessor2();
c.Process("foo"); // Error: no such method in CustomProcessor2

((IProcessor<string>)c).Process("foo"); // Works fine

This way of implementing is good for splitting interfaces. But it can also skip or replace default implementations without warning. If teams use both explicit and implicit ways to implement, it gets very hard to follow how methods are called.


Generic Constraints and Default Implementation Conflicts

Constraints in generic interfaces make things even more complex. Here’s an example:

public interface IService<T> where T : new()
{
    void Build()
    {
        Console.WriteLine("Building default T");
    }
}

However, if a child interface introduces its own constraint or a class implements the same method differently, how the method is chosen might go wrong:

public interface IServiceAdvanced<T> : IService<T> where T : class
{
    void IService<T>.Build()
    {
        Console.WriteLine("Advanced Build");
    }
}

Now calling Build() might call the advanced method. This can happen even if the first default method fits the situation better. These wider effects make it harder to maintain and find bugs.


Dispatch Table Mechanics: How the CLR Processes Interface Methods

Unlike normal ways objects act differently based on their class type, where the CLR uses virtual method tables (v-tables), default interface methods are called in a different way.

C# turns default interface methods into metadata. Code only finds and uses them when the program runs and they are called through an interface type. This means a few things:

  • Interface methods are not truly polymorphic in the class inheritance sense.
  • Static dispatch will not find default interface methods unless you use the right interface types.
  • If a class implements an interface but doesn’t replace a default method—and you call that method on the concrete type—you could get an error when compiling, or the method might not run at all.

Reference: Microsoft Proposal on Default Interface Implementations


Solutions: Best Practices for Reliable Default Handling

Here’s how to keep default implementations the same and less likely to cause errors:

✓ Consistent Use of Interface References

Ensure methods are always called using the interface type if you want to use the default code.

((IProcessor<string>)myProcessor).Process(item);

✓ Minimize Use of new Modifier

Use new carefully, and know when you're hiding a method rather than replacing it.

✓ Use Explicit Interface Implementation with Clear Intent

Only use explicit implementations when the interface needs to act in a special way. And write this down clearly for all your code.

✓ Composition Over Inheritance

Instead of making very big interface structures, try putting together smaller interfaces:

public class SuperProcessor
{
    private readonly IProcessor<string> _processor;
    private readonly IReporter _reporter;

    public void Run(string data)
    {
        _processor.Process(data);
        _reporter.Report(data);
    }
}

Common Mistakes and How to Debug

Real-world developers often have trouble with:

  • ❌ Assuming default methods act like base classes.
  • ❌ Referencing concrete classes when expecting interface behavior.
  • ❌ Mixing new and override without understanding dispatch flow.
  • ❌ Letting generics silently hide implementation paths.

Debugging Tips

  • Use Visual Studio “Go to Definition” to see which method will actually be called.
  • Use interface-specific unit tests to check if the right method is chosen.
  • Use warnings (like CS0108) to catch cases where method hiding happens in a tricky way.

Best Practices and Recommendations for Teams

For teams who work with big code projects or SDKs that spread out:

  • Document all default interface methods in XML comments and commit guidelines.
  • Test interfaces directly—write unit tests for IProcessor<T>, not just Processor classes.
  • Avoid placing very important business code in default methods—think of them as optional background work.
  • Prefer multiple small, focused interfaces over big, single generic ones.

Real-World Applications: Why Interface Behavior Matters

Default interface implementations are often used in:

  • 🔌 Plugin-based architectures (e.g., MEF or custom DI systems)
  • 📦 API contracts in public SDKs
  • 🧱 Cross-cutting systems like logging, validation, telemetry

If interfaces call the wrong methods in these areas, it can hurt security, lead to crashes that are hard to explain, or break SDKs. So, it is very important that things work as expected.


Recap: Checklist to Find Out Why Interface Behavior Is Missing

  • ✅ Are you using an interface-type reference during the call?
  • ✅ Have you accidentally hidden the method with the new modifier?
  • ✅ Are any generic constraints clashing with method availability?
  • ✅ Is the method implemented explicitly and not via interface call?
  • ✅ Would composition reduce method overlapping or confusion?

Further Reading & Learning Path

Want to improve your skills with .NET design patterns? Stay with Devsolus—your guide to learning the changing C# world.


References

Microsoft. (2019). What's new in C# 8.0. Retrieved from https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8

Lippert, E. (2020). Method hiding and override rules in C#. Retrieved from https://ericlippert.com/2020/01/27/method-hiding-and-override-rules-in-csharp/

Microsoft. (n.d.). C# Interface default implementations. Retrieved from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading