0

My project has many handler classes that derive from a common base class, and I am trying to get each of the handler classes to install themselves in a static table in the base class, and my first thought was a static constructor.

But this seems to use a super-lazy evaluation, and since I never refer to the derived classes directly (always by the handler table), this never triggers the static ctor.

Super simple stupid example:

public abstract class BlahBase
{
    public static readonly Dictionary<string, BlahBase> Handlers = new();

    public static BlahBase FindHandler(string name)
    {
        // look up in the Handlers table, return to caller
    }

    // other stuff
}

public class BlahFooHandler : BlahBase
{
    static BlahFooHandler()
    {
       Handlers.Add("foo", typeof(BlahFooHandler));
    }
    // other stuff
}

public class BlahBarHandler : BlahBase
{
    static BlahBarHandler()
    {
       Handlers.Add("bar", typeof(BlahBarHandler));
    }
    // other stuff
}

The idea is that BlahFooHandler and BlahBarHandler's static constructors would install themselves in the base Handlers table, and none of this apparently works without directly touching the derived class to trigger the static constructor. I'm trying to make this entirely dynamic.

I've spent much time looking into static constructors, and most questions revolve around lazy behavior: I want non lazy behavior.

An alternate approach is to use reflection and/or an [Attribute] on each of the handler classes, both of which I'm comfortable doing, but wonder if I'm missing an obvious language facility to achieve this.

Steve Friedl
  • 3,929
  • 1
  • 23
  • 30
  • https://stackoverflow.com/a/2654684/3181933 ? – ProgrammingLlama Dec 13 '22 at 04:45
  • Attributes are an adequate way of doing this, I believe - static initialization [by spec](https://www.ecma-international.org/wp-content/uploads/ECMA-334_5th_edition_december_2017.pdf) (15.5.6.2) will only run prior to the type being used (which, in your case, is never). Given the specifics, I would generally stay away from static init - it makes it very easy to accidentally change order by referencing the type somewhere. – YellowAfterlife Dec 13 '22 at 04:51
  • Using reflection to load types is currently the only choice. You can put handlers in an individual library then use Assembly class to load them all. – shingo Dec 13 '22 at 05:34
  • Have a look at C# 9.0's [Module Initializers](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/module-initializers). – Olivier Jacot-Descombes Dec 14 '22 at 20:09

2 Answers2

0

You should create a static class with a static constructor and remove the other static constructors then in one spot add the handlers

public static class BlahHelper
{
    public static readonly Dictionary<string, BlahBase> Handlers = new();
    
    static BlahHelper
    {
        Handlers.Add("foo", typeof(BlahFooHandler));
        Handlers.Add("bar", typeof(BlahBarHandler));
    }
    
    public static BlahBase FindHandler(string name)
    {
        // look up in the Handlers table, return to caller
    }

    // other stuff
}

public abstract class BlahBase
{
    // other stuff
}

public class BlahFooHandler : BlahBase
{
    // other stuff
}

public class BlahBarHandler : BlahBase
{
    // other stuff
}
sahlaysta
  • 95
  • 1
  • 3
0

The first thing I did was change your code so it would compile; you declare Handlers to be a Dictionary<string, BlahBase>, but you have code like:

   Handlers.Add("foo", typeof(BlahFooHandler));

Which adds Types to the dictionary, not BlahBase instances.

So, BlahBase now looks like:

public abstract class BlahBase
{
    public static readonly Dictionary<string, Type> Handlers = new Dictionary<string, Type>();

    public static BlahBase FindHandler(string name)
    {
        if (Handlers.TryGetValue(name, out var type))
        {
            var result = Activator.CreateInstance(type);
            return result as BlahBase;
        }
        return null;
    }
    // other stuff
}

The rest of your code stays the same.

The rule for static constructors is that they get invoked some time before the first time the type is used. So, what I did is this:

BlahFooHandler unused1 = new BlahFooHandler();
BlahBarHandler unused2 = new BlahBarHandler();

var blah = BlahBase.FindHandler("BlahFooHandler");

I create a dummy instance of each of my BlahBase subclasses somewhere before I call BlahBase.FindHandler. Each of those statements fires of the appropriate static constructore. Then, you away to the races.

For what it's worth, I tried just declaring references to instances of those types:

BlahFooHandler unused1;
BlahBarHandler unused2;

It didn't invoke the static constructors.

Flydog57
  • 6,851
  • 2
  • 17
  • 18