so

C# static initializers

Volume 5, Issue 9; 24 Jun 2021

A C# class initialization conundrum that has me stumped. If anyone can point me to an explanation of what’s going on here, I’d appreciate it.

Consider the following class:

class StaticInitializerConfusion<T> {
    private readonly string _name;
    private readonly T _defaultValue;

    private static Dictionary
      <string,StaticInitializerConfusion<T>> _index = new ();
    
    protected StaticInitializerConfusion(string name,
                                         T defaultValue) {
        _name = name;
        _defaultValue = defaultValue;
        if (_index.ContainsKey(name)) {
            Console.WriteLine("DUP: " + name);
        }
        else {
            Console.WriteLine("INIT: " + name);
            _index.Add(name, this);
        }
    }

    public string GetName() {
        return _name;
    }

    public static readonly StaticInitializerConfusion<List<string>> VALUE1 = new(
        "name1", new());

    public static readonly StaticInitializerConfusion<List<string>> VALUE2 = new(
        "name2", new());

    public static readonly StaticInitializerConfusion<bool> VALUE3 = new(
        "name3", true);
}

It’s been stripped of everything useful but I hope the intent is still clear. Actually, naming it StaticInitializerConfusion may have masked the intent entirely. This is my attempt to port the Java ResolverFeature class to C#.

The idea is that there’s a generic feature class with some static fields that provide constant feature values. This let’s you do things like:

config.setFeature(ResolverFeature.FEATURE_NAME, typedValue);

in Java and, I expect, things like this in C#:

config.SetFeature(ResolverFeature<bool>.FEATURE_NAME, typedValue);

Java generics and C# generics work a little differently and it’s very likely that I’m just completely misunderstanding something about C# generics (and/or Java generics, if it comes to that).

Back to my sample class above. If I stick that in a program and run

Console.WriteLine(
  StaticInitializerConfusion<List<string>>.VALUE1.GetName());

I naïvely expect to see:

INIT: name1
INIT: name2
INIT: name3
name1

What I actually see is:

INIT: name1
INIT: name2
DUP: name1
DUP: name2
INIT: name3
DUP: name3
name1

and I’m at a loss to explain why. I expect the static fields to be initialized exactly once. My expectation is reinforced by the Microsoft docs on static constructors which say:

A static constructor is used to initialize any static data, or to perform a particular action that needs to be performed only once. It is called automatically before the first instance is created or any static members are referenced.

If I remove the code that attempts to store the constants in the _index dictionary (in the real class, the dictionary is used to support “get-feature-by-name” and “get-iterator-over-all-features” methods) the problem goes away. So there’s some sort of interaction going on there. But what?

The solution may be to abandon generics altogether here and do something else. Even if this problem didn’t arise, there appears to be no way in C# to write a method that can return any feature. That is, the equivalent of this Java method:

public static ResolverFeature<?> byName(String name) {
    return index.get(name);
}

Which is kind of a bummer.

Regardless, for my own edification if nothing else, pointers to the docs or the specs or the blog postings that explain how I’m being an idiot this time most appreciated!