C# 8 Default Interface Methods & Static Fields: Which Version? Complete Guide with Examples

Jun 16, 20267 min read

Share|

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

FeatureC# VersionMinimum 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 interfacesC# 8.0.NET Core 3.0+
Private / protected / internal interface membersC# 8.0.NET Core 3.0+
Sealed override in interfacesC# 8.0.NET Core 3.0+
Static abstract members in interfacesC# 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
csharp
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: 

csharp
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:  

csharp
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. :

csharp
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:

csharp
> _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:

csharp
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 virtual nor sealed are specified, we assume the member is abstract.
  • if there is an implementation and neither abstract nor sealed are specified, we assume the member is virtual .
  • sealed modifier is required to make a method neither virtual nor abstract.

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 abstract method stays abstract.
  • virtual method stays virtual .
  • a method without any modifier stays neither virtual , nor abstract.
  • sealed modifier 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:

csharp
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:

csharp
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 IRetryPolicy with a default ExecuteAsync — 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.

Related content on MatrixTrak:
Axiom Pack
$49

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
Get Retry Policy Kit →

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

Next step

Minimal APIs, OpenTelemetry, idempotency, and legacy .NET rescue patterns.

Explore .NET Production Reliability →