What is the idiomatic use case for static fields inside interfaces?
Asked Answered
B

2

6

I've searched through this website for anything tagged and found that the topic rarely comes up. Examples from other websites have been similarly unsatisfactory and I've yet to come up with anything myself. I've therefore found it difficult to think of a use case for interfaces that include static fields.

Is there any design pattern, or any other idiom, that recommends the usage of a static field inside of an interface?

Bonnard answered 1/10, 2022 at 14:30 Comment(3)
Any constant values related to the domain of the interface would make sense as static fields, right?Godewyn
@Godewyn But at that point, why not define them in the class/namespace to which the interface belongs?Bonnard
@Godewyn I agree that having domain specific constants in the interface would make sense, but I'd rather use a real const here than a static field.Wystand
B
10

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:

  1. 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.

  1. 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
Bridie answered 1/10, 2022 at 15:18 Comment(4)
"Static fields and other members not being part of the public interface are there to support the implementation of those default interface methods" - Citation?Bonnard
The document csharplang/proposals/csharp-8.0/default-interface-methods.md on Github says: "[...] Static and private methods permit useful refactoring and organization of code used to implement the interface's public API. [...]".Bridie
It's interesting that you don't mention any use case for direct references to the static fields of the interface from outside of the interface's own code. It's as if there's no good use case for that ability.Bonnard
I prefer to put those things into a static class. It doesn’t feel right to have them in an interface, and I do not see a compelling reason for it. This is different for C# 11’s static virtual members being part of the API. They are useful in type constraints: class C<T> where T : I allows accessing static members from T, e.g., a factory method: var c = T.Create("key");. The constraint where T : new() allows only a default constructor: var c = new T();. The Create method can also return types derived from T, what new T() can of course not.Bridie
P
0

It looks like there are two main reasons such as

  • to store a value that must be shared among all instances
  • you can count some value between all instances
  • to avoid recreation of an instance of static field value each time

Let me show a example:

public interface IFoo
{
    private static string staticField;

    public static string GetStaticField()
    {
        if (staticField is null)
            staticField = $"DateTime is {DateTime.Now}";

        return staticField;
    }
}

or shorter version will look like this:

public static string GetStaticField()
{   
    staticField ??= $"DateTime is {DateTime.Now}";

    return staticField;
}

and then it can be seen that if staticField will be assigned, then it never be reassigned:

var result_1 = IFoo.GetStaticField();
Console.WriteLine(result_1);

Thread.Sleep(10000); // imitation of hard work

var result_2 = IFoo.GetStaticField();
Console.WriteLine(result_2);

Output:

DateTime is 2022.10.12 05:10
DateTime is 2022.10.12 05:10
Poole answered 2/10, 2022 at 13:10 Comment(4)
This will create a new instance each time.Bridie
You've missed assigning to staticField.Unprepared
@OlivierJacot-Descombes thank you for your comment. However, I made a test and it looks like it keeps staticField always the same. In my view, it happens because static field is already instantiated. Please, see my updated answerPoole
@StepUp, the idiomatic way to implement the lazy initialization of the field is if(field is null) { field = new Value(); } return field;. This requires only one return statement which works in both cases. In your first implementation the field was never returned because it was never assigned. Instead, a new object was returned every time.Bridie

© 2022 - 2024 — McMap. All rights reserved.