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!

Comments

The simple answer is that you are not using static constructor. As mentioned documentation says: A static constructor doesn't take access modifiers or have parameters.

In the StaticInitializerConfusion<T> class, there are static readonly members used (VALUE1, VALUE2, and VALUE3), but the constructor

protected StaticInitializerConfusion(string name,
                                         T defaultValue)

is called each time new instance of this class is initiatated. And the initialization is made for each read only property:

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

The most important (and surprising to me) part of the problem is, that in the case of the demo code, there will be 2 static instances of _index member created. First one for StaticInitializerConfusion<List<string>> values, and second one for StaticInitializerConfusion<bool> values.

An _index with type StaticInitializerConfusion<List<string>> can accept only VALUE1, VALUE2, and the _index with type StaticInitializerConfusion<bool> can accept only VALUE3. This is why INIT: name3 occurs only once.

If you change your code in the constructor to something like this:

Console.WriteLine($"{name}; {this.GetType()}");
_name = name;
_defaultValue = defaultValue;
if (_index.ContainsKey(name))
{
    Console.WriteLine($"DUP: {name}; {_index.GetType()}; {this.GetType()}");
}
else
{
    Console.WriteLine($"INIT: {name}; {_index.GetType()}; {this.GetType()}");
    _index.Add(name, this);
}

you will see, that when the type of the initiated class is changed (instead of StaticInitializerConfusion<List<string>> there will be StaticInitializerConfusion<bool> for name3), constructor for name1 and name2 is called, because there will be new static _index member with different type.

—Posted by Boris Lehečka on 24 Jun 2021 @ 09:41 UTC #

You can create an index of all public static fields using reflection and LINQ. In this case a static constructor is the right place. Unfortunately as the C# has no concept of the wildcard generics (aka ResolverFeature<?>) values are stored as objects in the Dictionary.

using System.Linq;
using System.Reflection;


class StaticInitializerConfusion<T>
    {
        
        private static Dictionary <string, object> _index;

        static StaticInitializerConfusion()
        {
            var staticFields = typeof(ResolverFeatures)
                .GetFields(BindingFlags.Public | BindingFlags.Static)
                ;

            _index = staticFields.ToDictionary(x => x.Name, x => x.GetValue(null));
        }

        public static IEnumerable<string> GetNames()
        {
            return _index.Keys;
        }
    }
—Posted by Boris Lehečka on 25 Jun 2021 @ 08:48 UTC #

Thank you, Boris. That's a very helpful explantation. I've set aside the attempt to use generics for the moment and switched to simply using a class hierarchy. That means more explicit casts are required, but it avoids these particular headaches.

Perhaps when I have more C# experience under my belt, I'll be able to come back and revisit this.

—Posted by Norman Walsh on 25 Jun 2021 @ 04:24 UTC #

Please provide your name and email address. Your email address will not be displayed and I won’t spam you, I promise. Your name and a link to your web address, if you provide one, will be displayed.

Your name:

Your email:

Homepage:

Do you comprehend the words on this page? (Please demonstrate that you aren't a mindless, screen-scraping robot.)

What is six times three?   (e.g. six plus two is 8)

Enter your comment in the box below. You may style your comment with the CommonMark flavor of Markdown.

All comments are moderated. I don’t promise to preserve all of your formatting and I reserve the right to remove comments for any reason.