With C# 8.0, Microsoft introduced Default Interface Methods (DIM), allowing developers to add default implementations to interface members. However, many developers encounter a frustrating roadblock when trying to access those default properties or methods directly from a class instance.

If you have ever tried to consume a default interface implementation on a concrete class instance and ended up with a compiler error or a StackOverflowException, you are not alone. Let's break down why this happens and explore the correct ways to resolve it.

Understanding the Problem

Consider the following setup:

public interface I
{
    public int MyProperty => 10;
}

public class C : I
{
    // How do we expose MyProperty here?
}

When attempting to call new C().MyProperty, three common attempts usually fail:

  1. Auto-property: Declaring public int MyProperty { get; } in class C does not use the default value; it defaults to 0 because it acts as a brand-new auto-property.
  2. Omitting the property in class C: Trying to call new C().MyProperty throws a compilation error: "'C' does not contain a definition for 'MyProperty'".
  3. Casting to the interface: Writing public int MyProperty => ((I)this).MyProperty; causes a runtime StackOverflowException.

Why Does the StackOverflowException Occur?

Default interface methods use virtual dispatch. When you write ((I)this).MyProperty, the runtime inspects the actual instance type (C) to find the most specific implementation. Because class C defines MyProperty, the runtime dispatches right back to C.MyProperty, resulting in an infinite recursive loop.

Why Isn't the Property Visible on the Class?

In C#, default interface implementations are always explicit. Classes do not inherit members of an interface into their public surface area. This design prevents the "diamond problem" associated with multiple inheritance.

The Solutions

Solution 1: Program Against the Interface (Recommended)

The idiomatic C# approach for consuming default interface implementations is to treat the instance as the interface type rather than the concrete class type.

// Store as the interface type
I instance = new C();
Title = instance.MyProperty.ToString(); // Outputs: 10

// Or cast inline
Title = ((I)new C()).MyProperty.ToString();

If your class does not provide its own implementation of MyProperty, accessing it via reference to I will automatically use the default implementation defined in the interface.

Solution 2: Use an Abstract Base Class Instead

If you want consumers of C to access MyProperty directly without casting or referencing an interface, an abstract base class is the appropriate object-oriented design tool.

public abstract class BaseClass
{
    public virtual int MyProperty => 10;
}

public class C : BaseClass
{
    // Inherits MyProperty automatically on its public surface
}

// Usage:
Title = new C().MyProperty.ToString(); // Works directly!
Rule of Thumb: Use Default Interface Methods to evolve APIs without breaking backward compatibility. Use abstract classes when you want classical implementation inheritance and shared public class APIs.

Solution 3: Delegate to a Static Helper or Constant

If you must expose MyProperty directly on class C while sharing the logic with interface I, extract the default value to a shared constant or static method:

public interface I
{
    public const int DefaultProperty = 10;
    public int MyProperty => DefaultProperty;
}

public class C : I
{
    public int MyProperty { get; set; } = I.DefaultProperty;
}

// Usage:
Title = new C().MyProperty.ToString(); // Returns 10

Summary

  • Classes do not automatically inherit default interface implementations into their public API.
  • Calling ((I)this).Member inside Class.Member creates infinite recursion because virtual dispatch redirects back to the class member.
  • To use the default implementation, reference the object through the interface (e.g., ((I)obj).Property) or refactor to an abstract base class if public inheritance is required.