Default Interface Implementations in C#: Why They Exist and How to Use Them Wisely

C# developers love interfaces. They let us define contracts, enforce consistency, and decouple our code in ways that inheritance never could. But if you’ve been working in C# long enough, you probably remember a certain pain point that came with them — a small but persistent one that could cause huge headaches in larger projects.

That pain point was this: once you shipped an interface, you could never safely change it.

Let’s say you defined an interface in your public API:

A simple logging interface

Everything’s fine for a while. Then, a few months later, you realize it would be nice to have a method that supports logging exceptions too. Seems simple enough, right?

Now you want to be able to log exceptions that arise too

But the moment you add that new method, every single class that implemented ILogger breaks. The compiler yells at you to implement the new member, forcing you to go through every logging class in your solution — and potentially every third-party library that depended on your interface.

That’s where Default Interface Implementations come in.

The Problem DIIMs Solve

Default Interface Implementations (or DIIMs for short) were introduced in C# 8.0, and they fundamentally change what an interface can do.

Before C# 8, interfaces were purely contracts — they couldn’t contain any implementation at all. You could declare what methods existed, but not how they behaved.

C# 8 changed that by allowing you to define default behavior directly inside an interface method, like this:

What’s this wizardry?! An implementation inside an interface??

With this change, you can add new members to an interface without breaking every existing implementation. If a class doesn’t override the new member, it simply inherits the default implementation.

In our example, all existing ILogger implementations still compile — even though they never defined Log(Exception ex) — because the interface itself now provides a fallback definition.

This might sound like a small thing, but it’s actually a huge shift in how .NET frameworks and large codebases can evolve over time.

Why C# Needed This Feature

The idea behind DIIMs wasn’t to give developers another syntactic trick. It was born out of necessity — especially for the teams maintaining massive, widely used frameworks like .NET itself.

Imagine you’re part of the .NET team, and you maintain something like IEnumerable<T>. You realize a new method, say Count(), would be useful as part of the interface. But you can’t add it without breaking every library in existence that implements IEnumerable<T>. That’s an impossible situation.

For years, the .NET team worked around this limitation by introducing extension methods — static methods that add functionality to interfaces without modifying them. That’s how we got LINQ.

Extension methods were a brilliant hack, but they still couldn’t add true behavior to the interface itself. They lived outside it.

Default Interface Implementations finally closed that gap. Now, framework designers can evolve interfaces safely by introducing new members with default bodies, ensuring backward compatibility without forcing downstream developers to rewrite code.

How It Actually Works

Under the hood, a default interface implementation is compiled a bit like a virtual method on a base class. When the runtime encounters a call to that interface method, it looks for the most specific implementation available — starting with the concrete class.

If the class doesn’t provide its own implementation, the runtime simply executes the version defined on the interface itself.

That means you can now think of interfaces as having a tiny bit of inheritance-like behavior. They’re still not classes — they don’t maintain state, and they don’t replace traditional inheritance — but they’ve gained just enough flexibility to make versioning safer.

Here’s an example:

Even though FriendlyGreeter never defines Greet(), it still works — because the default implementation in the interface kicks in.

This was unthinkable before C# 8.

How This Differs from Abstract Classes

At first glance, DIIMs might look like abstract classes — after all, both can define methods with behavior. But they serve different roles. An abstract class defines both structure and state. It can have fields, constructors, private helpers, and full inheritance hierarchies. It’s a partial implementation meant to be extended by subclasses.

An interface, even with DIIMs, is still just a contract. It can’t hold instance fields or constructors. Its default methods are more like “fallbacks” — they’re there for convenience or backward compatibility, not for sharing common internal logic.

The biggest philosophical difference is that abstract classes define how things work together, while interfaces define what things can do.

Default implementations don’t change that; they just make interfaces a bit more forgiving when they evolve.

When You Should Use Them

In most day-to-day C# development, you’ll rarely need to create your own default interface implementations. Their primary use case is in library and framework design — places where maintaining backward compatibility is critical.

For example, if you release a NuGet package with an interface, and later you want to add an optional method to it, DIIMs let you do that without forcing every consumer to recompile their code.

Here’s a practical scenario:

Adding a method to export JSON

Existing implementations of IExporter don’t need to do anything to get the new method — they just inherit it automatically. But if a specific exporter (say, one that writes XML) wants to override the behavior, it can still do so freely.

This approach keeps your API clean and backwards-compatible without breaking consumers.

When You Shouldn’t Use Them

While DIIMs are incredibly useful, they also introduce subtle risks. Because they blur the line between abstract and concrete behavior, they can easily lead to confusion if overused.

For one thing, developers reading your code might not realize that a method defined on an interface actually does something. That violates the long-standing expectation that interfaces describe behavior but never implement it.

Worse, it can complicate multiple inheritance scenarios. If a class implements two interfaces that both define default methods with the same signature, you’ll need to disambiguate explicitly in your class:

You can see how things start getting messy quickly

That’s why most C# experts recommend treating DIIMs as a tool for framework evolution, not for everyday design. If you find yourself adding logic-heavy default methods, it’s a sign you probably should’ve used an abstract class or a helper instead.

A Philosophy Shift in C#

When you zoom out, Default Interface Implementations are part of a bigger story about how C# has evolved over the years.

The language started off rigid and conservative, enforcing strict separation between interface and implementation. But as C# matured — especially with features like async/await, records, pattern matching, and DIIMs — it’s embraced flexibility and expressiveness.

C# is no longer afraid of abstraction that bends the rules a little, as long as it makes code easier to maintain and reason about.

DIIMs are a perfect example of that balance: powerful enough to solve real problems, but designed to stay out of your way unless you truly need them.

Wrapping Up

Default Interface Implementations may not be something you reach for every day, but they quietly fix one of the oldest headaches in C# development. They let you evolve interfaces safely, preserve backward compatibility, and reduce breaking changes — all without abandoning the simplicity of interface-based design.

If you think of interfaces as contracts, DIIMs are the “amendments” that let you evolve those contracts responsibly. They don’t rewrite the constitution — they just make it a little easier to live with over time.

Want to continue the discussion? Join us over at our Sugar Shack FB group!

Next
Next

The Unity Component System: Why Composition Beats Inheritance