Traditionally interfaces are just contracts and do not contain executable code or state information but only contain abstract instance members that must be implemented by a class or a struct. Two features introduced in C# 8.0 and C# 11 add static members to interfaces. Only the first one adds executable code to the interface and possibly with it the need to save state:
In C# 8.0 Microsoft has introduced default interface methods. It is optional for a class to implement these methods. If they are not implemented, the default implementations kick in. However, these methods are not inherited by the class and can only be called through the interface (even within the class implementing the interface).
The primary motivation is to allow extending an interface without breaking existing code.
Static fields and other members not being part of the public interface are there to support the implementation of those default interface methods. The C# language proposal for default interface methods says:
Interfaces may not contain instance state. While static fields are now permitted, instance fields are not permitted in interfaces. Instance auto-properties are not supported in interfaces, as they would implicitly declare a hidden field.
Static and private methods permit useful refactoring and organization of code used to implement the interface's public API.
- Yet another feature is coming with C# 11: static virtual members in interfaces. Those must be implemented by the implementing class. They were primarily introduced to allow formulating interfaces for numeric types who have static operator methods (+, -, *, /, etc.). But they also allow to declare static factory methods. These interfaces are useful in generic type constraints.
Here is a full use case. New properties are added to an interface. To not break existing implementations, they are added with a default implementation.
Since we cannot store instance state in an interface (interfaces are not instantiated), we cannot use instance backing fields for the properties. Instead, we use a dictionary with object references as key to store the property values. (We could also use Weak References, but that's beyond the scope of this article.) The dictionary is stored in a private static field. This is comparable to how WPF stores dependency properties.
interface IUseCase
{
string Name { get; set; } // Public by default, must be implemented by the class
// New property added in a later release with a default implementation.
// Implementation by class is optional.
private static Dictionary<IUseCase, string?> _firstNames =
new(ReferenceEqualityComparer.Instance);
string? FirstName
{
get {
_firstNames.TryGetValue(this, out string? s);
return s; // s is null when TryGetValue returns false
}
set {
_firstNames[this] = value;
}
}
// New property with default implementation.
string FullName => $"{FirstName} {Name}";
}
A class created before the interface was extended implementing only the first property.
class UseCase : IUseCase
{
public UseCase(string name)
{
Name = name;
}
public string Name { get; set; }
}
Test:
IUseCase u1 = new UseCase("Doe");
u1.FirstName = "John";
IUseCase u2 = new UseCase("Poe");
u2.FirstName = "Jane";
Console.WriteLine(u1.FullName);
Console.WriteLine(u2.FullName);
Prints:
John Doe
Jane Poe
const
here than a static field. – Wystand