
Category:.NET
C# 8 Default Interface Methods & Static Fields: Which Version? Complete Guide with Examples
Which C# version introduced default interface methods and static fields? C# 8.0. Complete reference covering static methods, fields, access modifiers, sealed/virtual members, CLR support, and production use cases with code examples.
Free download: Polly Retry Policies package. Jump to the download section.
Paid pack available. Jump to the Axiom pack.
Version Quick Reference
| Feature | C# Version | Minimum Runtime |
|---|---|---|
| Default interface methods (virtual extension methods) | C# 8.0 | .NET Core 3.0+ |
| Static members in interfaces (fields, methods, properties) | C# 8.0 | .NET Core 3.0+ |
| Static fields in interfaces | C# 8.0 | .NET Core 3.0+ |
| Private / protected / internal interface members | C# 8.0 | .NET Core 3.0+ |
| Sealed override in interfaces | C# 8.0 | .NET Core 3.0+ |
| Static abstract members in interfaces | C# 11 | .NET 7+ |
Can interfaces have default implementations? Yes, since C# 8.0.
C# 8.0 introduced default interface methods (also called virtual extension methods), allowing interfaces to provide concrete implementations. Before C# 8, interfaces could only declare method signatures — implementing classes had to provide all the code. This changes that.
Can interfaces have static fields? Yes, since C# 8.0.
C# 8.0 also allows interfaces to declare static members, including fields, methods, properties, events, and static constructors.
When were static methods in interfaces introduced? C# 8.0.
Static interface methods were introduced in C# 8.0 alongside default interface methods. C# 11 later added static abstract interface methods for generic math scenarios.
Let's talk about the "default implementation" and what benefit it brings.
Earlier we used to have interface implemented in a number of classes. If we need a new member we used to:
- Either we update all the classes to support the new member.
- Or we extend the interface with new method/member and inherit with the existing interface
interface ICar
{
void GetSpeed();
void GetMilage();
}
interface IAutonomousCar: ICar
{
void SendCommand();
}I guess we all know where we ended up with such implementation. The list of extended interfaces grows till it becomes unmanageable.
C# 8 answers to this by introducing “Default implementation in interfaces” which adds support for virtual extension methods – methods in interface with a concrete implementation.
Concrete methods in interfaces
We can now add members to interface and provide an implementation for those members. This feature enables an API author to add methods to an interface in a future version without breaking source or library with the existing implementation. Very much like Java’s “Default Methods“.
By the way, this feature requires support in the CLI/CLR — programs that take advantage of it cannot run on earlier versions of the platform.
Following class implements this interface need not implement its concrete method:
interface ICar
{
void GetSpeed();
void GetMilage();
void SendCommand()
{
Console.WriteLine("Command Send via Interface");
}
}
class ToyotaGarage : ICar
{
public void GetSpeed()
{
Console.WriteLine("200 KMPH");
}
public void GetMilage()
{
Console.WriteLine("10 KM Per Liter");
}
}We did not implement SendCommand method into the class
ToyotaGarage. However, the default implementation will be available from an instance of the interface:
class Program
{
static void Main(string[] args)
{
ICar tg = new ToyotaGarage();
tg.SendComand(); // prints "Command Send via Interface"
}
}C# 8 now allow us to override a concrete method of the interface in the inherited class. :
class TeslaGarage : ICar
{
public void GetSpeed()
{
Console.WriteLine("350 KMPH");
}
public void GetMilage()
{
Console.WriteLine("50 KM Per Charge")
}
override void SendCommand()
{
Console.WriteLine("Command Send via TeslaGarage");
}
}In this example, TeslaGarage simply override the concrete method SendCommandof the interface.
Overrides in interfaces
The override statement allows us to provide the most specific implementation of a virtual member in the interface, where the member will not be found by the compiler or runtime. It also allows turning an abstract member from a super-interface into a default member in a derived interface.
In simple a derived interface can provide a more appropriate default implementation by explicitly implementing the base member:
> _Override declaration in interfaces may not be declared sealed._
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override void IA.M() { WriteLine("IB.M"); } // explicitly named
}
interface IC : IA
{
override void M() { WriteLine("IC.M"); } // implicitly named
}We explicitly implement the base member by including IA. in the name. Without IA., the compiler would warn us to either make it explicit or use the new keyword if we want to hide it.
Modifiers in interfaces
C# 8 now support modifiers on its members such as : private, protected, internal, public, virtual, abstract, sealed, static, extern, and partial.
Internal members only can be accessed through the derived interface
An interface member with a concrete method is by default a virtual member unless explicitly sealed or private modified are used. virtual members cant be overridden by class, the only derived interfaces can override it:
public interface ICar
{
public virtual void SendCommand()
{
Console.WriteLine("Command Sent via Interface");
}
}
public interface IAnotherCar :ICar
{
void ICar.SendCommand()
{
Console.WriteLine("Command Sent via another Interface");
}
}
class MorrisGarage: ICar, IAnotherCar
{
}
class Program
{
static void Main()
{
ICar mg= new MorrisGarage();
mg.SendCommand(); //Calls the virtual implementation.
IAnotherCar mgOverridden = new MorrisGarage();
mgOverridden.SendCommand(); //Calls the overridden implementation.
}
}Virtual Modifier vs sealed Modifier (Source)
It is allowed that modifiers to be stated on interface members unless there is a reason to disallow some of them. This brings an interesting question around virtual modifier. Should it be required on members with default implementation?
We could say that:
- if there is no implementation and neither
virtualnorsealedare specified, we assume the member isabstract. - if there is an implementation and neither
abstractnorsealedare specified, we assume the member isvirtual. sealedmodifier is required to make a method neithervirtualnorabstract.
Alternatively, we could say that virtual modifier is required for a virtual member. I.e, if there is a member with implementation not explicitly marked with virtual modifier, it is neither virtual, nor abstract. This approach might provide a better experience when a method is moved from a class to an interface:
- an
abstractmethod staysabstract. - a
virtualmethod staysvirtual. - a method without any modifier stays neither
virtual, norabstract. sealedmodifier cannot be applied to a method that is not an override.
What do you think? comment below
Similar can be applied on abstract modifier, although
abstract is a default on all members (without body or concrete method) of the interface, that modifier may be given explicitly. Although every class need to implement the interface methods.
protected members are not accessible by derived class but via the derived interface. If a class requires to implement protected member, this has to be done through interface explicitly:
public interface ICar
{
public void SendCommand()
{
Console.WriteLine("Command Sent via Interface");
}
protected void SendCriticalCommand()
{
Console.WriteLine("Critical Command Sent via Interface");
}
}
public interface IAnotherCar : ICar
{
public void Send(bool bCritical)
{
if (bCritical)
this.SendCriticalCommand();
else
Console.WriteLine("Command Sent via Morris Garage Class");
}
}Last but not least, the interface can declare a
static member, including nested types, methods, properties event and static constructors.
CLR support API
In order for compilers to detect when they are compiling for a runtime that supports this feature, libraries for such runtimes are modified to advertise that fact through the API discussed in https://github.com/dotnet/corefx/issues/17116. As it add:
namespace System.Runtime.CompilerServices
{
public static class RuntimeFeature
{
// Presence of the field indicates runtime support
public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
}
}Abstract classes vs. interfaces in C# 8.0
Now the question arises if abstract classes and interfaces are the same in new C#? Although they are very similar in more ways than one, there are subtle differences between the two. We cannot extend a class and unlike an abstract class, the interface can not have instance members and you still can implement several interfaces.
Production Applications: Default Interface Methods in .NET Resilience
Default interface methods aren't just a language feature — they enable real production patterns. The trait-like composition they provide is used by resilience libraries to layer retry, circuit breaker, and timeout behaviors without forcing inheritance hierarchies.
Where this matters in production systems:
- A resilience library defines
IRetryPolicywith a defaultExecuteAsync— consumers override only what they need - New capabilities (telemetry, metrics, logging) can be added to an interface without breaking every existing implementation
- Policy decorators and middleware handlers compose behaviors at the interface level without forced base class chains
This pattern is especially relevant for .NET production systems where retry policies, circuit breakers, and timeout handling must be composed cleanly across services.
- Polly Retry Policies Done Right — production-grade retry with backoff, jitter, caps, and stop rules in .NET 8
- Retry Logic: When Resilience Makes Outages Worse — the real cost of retry amplification
- Exponential Backoff with Jitter Explained — stop retry storms with proper backoff
- Timeouts First — why infinite waits create recurring outages in .NET
Retry Policy Kit: Battle-Tested Resilience for Production
Managing retries across multiple services? Get pre-configured Polly policies with monitoring integration, circuit breaker patterns, and incident runbooks. Stop debugging retry storms in production.
- ✓10+ production-grade Polly policies for HTTP, gRPC, and database calls
- ✓Circuit breaker + retry coordination patterns
- ✓Monitoring integration (Prometheus, OpenTelemetry, Application Insights)
- ✓Incident runbooks for retry storm diagnosis and mitigation
Conclusion
Default interface methods in C# 8.0 let API authors add new members to interfaces without breaking existing implementations. Combined with support for static fields, access modifiers, and sealed overrides, this feature bridges the gap between abstract classes and interfaces — giving developers more flexible composition for evolving APIs.
If you're building production .NET systems that handle retries, timeouts, and resilience patterns, understanding default interface methods helps you design cleaner abstraction layers that can evolve without breaking changes.
Recommended resources
Download the shipped checklist/templates for this post.
A small shipped kit for safe Polly retries: C# client wrapper, retry checklist, retry logging schema, and setup notes.
resource
Related posts

Polly Retry Policies Done Right: Backoff, Jitter, Caps & Stop Rules (.NET 8)
When retries amplify failures instead of fixing them: how retry storms happen in .NET, how to prove it, and the four components that stop cascading failures.

C# Data Structures and Algorithms Tutorial: Production Examples with Code
Complete C# data structures and algorithms tutorial with production code examples. Learn arrays, dictionaries, queues, stacks, linked lists, trees, graphs, and sorting/searching algorithms used in real .NET applications. Includes time complexity cheat sheet and trading system examples.

Performance triage in legacy .NET: find the top 3 bottlenecks fast
When the legacy system is slow and no one knows where to start, a structured triage finds the real bottlenecks in hours, not weeks. This playbook gives you a repeatable method to identify, rank, and fix the top 3 performance killers.
Next step
Minimal APIs, OpenTelemetry, idempotency, and legacy .NET rescue patterns.