1

Say I have the following interface:

public interface IFoo
{
    public int Number { get; }
    public string Text { get; }

    public static int StaticNumber { get; }
    public static string StaticText { get; } // Nullable warning.
}

Normally, all of these properties should work. However, if I have C#'s nullable setting enabled, then IFoo.StaticText gives me the following warning:

Non-nullable property 'StaticText' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [ProjectName] csharp(CS8618)

Why does an interface property suddenly require an initial value to adhere to the nullable setting? Isn't that the job of the classes that implement IFoo?

I can only seem to fix it by giving it an initial value, but when the property is meant to be an interface for some calculated value, then I end up implementing too much within the interface, and the whole point of an interface is that I shouldn't have to have any implementation details yet, right?


Here's an example of why I I don't want to set an initial value (though I'm sure there are many reasons to avoid initial values in interfaces):

using System.Collections.Generic;

public interface IDatabase<int, T>
{
    public static IReadOnlyDictionary<int, T> All { get; }

    public static void Init() { }
}
public class ItemDB : IDatabase<int, Item>
{
    public static bool Initialized { get; private set; } = false;

    static Dictionary<int, Item> _all = new();
    public static IReadOnlyDictionary<int, Item> All
    {
        get
        {
            if (!Initialized) Init();
            return _all;
        }
    }

    // You'd probably initialize by pulling from a file or by making a database request.
    // But for this example, Init() just adds hardcoded items.
    public static void Init()
    {
        _all = new();

        _all.Add(new Item("Item 1"));
        _all.Add(new Item("Item 2"));
        _all.Add(new Item("Item 3"));
        _all.Add(new Item("Item 4"));
        _all.Add(new Item("Item 5"));
    }
}
public class Item
{
    public string Name { get; private set; } = "";

    public Item(string name)
    {
        Name = name;
    }
}
Tuckertcs
  • 29
  • 5

2 Answers2

4

You are confusing two features of C# here: static interface members and static virtual members.

Static interface members allows you to include members somewhat akin to a base class, which means all the values need to be initialised or you get null warnings. If you want the derived class to implement that property, then you need to mark it as abstract. For example:

public interface IFoo
{
    public int Number { get; }
    public string Text { get; }

    public abstract static int StaticNumber { get; }
    public abstract static string StaticText { get; } // No nullable warning.
}
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • Thanks for the quick help! Interesting quirk but a simply workaround. I'm getting an error with the abstract change though: `The modifier 'abstract' is not valid for this item in C# 10.0. Please use language version '11.0' or greater. [ProjectName] csharp(CS8703)`. Is there a way to do this in C# 10 or do I need to upgrade to 11? – Tuckertcs Jun 13 '23 at 15:21
3

Static members on an interface in C# aren't what you think they are, they are actual members, not abstracts for an implementing class, so they must have an implementation themself.

Hence C# generates the full pattern for the { get; } getter on the static property, and then there's a field without a default value. To say it more clearly, it'd be valid to actually access IFoo.StaticText, which would then be null.

Furthermore, this means that ItemDB.Init() hides IDatabase.Init(). Same goes for ItemDB.All hiding IDatabase.All.

Cobra_Fast
  • 15,671
  • 8
  • 57
  • 102
  • Thank you, this helps explain a related issue where the classes wouldn't throw warnings when I don't implement the properties yet. – Tuckertcs Jun 13 '23 at 20:58