2

I have two classes that I'm trying to use to implement a string enumeration pattern. The problem is the static constructor for the child class isn't being called when an operator of the parent class is called. Is there a way I can fix that without adding a hack to the code to initialize the static members?

public abstract class BaseStringEnum<T> where T : BaseStringEnum<T>
{
    public string Value { get; private set; }

    private static List<T> _values = null;
    protected static List<T> Values 
    {
        get
        {
            if (_values == null)
            {
                _values = new List<T>();
            }

            return _values;
        }
    }

    protected BaseStringEnum(string value, string resId)
    {
        Value = value;
        ResourceId = resId;

        Values.Add((T)this);
    }

    public static implicit operator string(BaseStringEnum<T> value)
    {
        return value.Value;
    }

    public static implicit operator BaseStringEnum<T>(string value)
    {
        return Values.Where(v => v.Value.Trim() == value.Trim()).First();
    }
}


public sealed class UseTimeStringEnum : BaseStringEnum<UseTimeStringEnum>
{
    private UseTimeStringEnum(string value, string resourceId) : base(value, resourceId) { }

    public static readonly UseTimeStringEnum None;// = new UseTimeStringEnum("N", "None");
    public static readonly UseTimeStringEnum Required;// = new UseTimeStringEnum("R", "Required");
    public static readonly UseTimeStringEnum Optional;// = new UseTimeStringEnum("O", "Optional");

    static UseTimeStringEnum()
    {
        None = new UseTimeStringEnum("N", "None");
        Required = new UseTimeStringEnum("R", "Required");
        Optional = new UseTimeStringEnum("O", "Optional");
    }
}

The problem is when the code tries to do something like (UseTimeStringEnum)"R" it fails because the static constructor hasn't fired yet. I feel like it should fire because I'm using a static operator.

Mykroft
  • 13,077
  • 13
  • 44
  • 72
  • The parent class doesn't know about the static constructor in the child class, which is why the constructor isn't called when you refer to the parent class. Even if you changed the classes to structs, it wouldn't make a difference. – BoltClock Sep 18 '14 at 02:55
  • 1
    What do you mean "fails"? – Peter Ritchie Sep 18 '14 at 04:16
  • I see only base class and subclass here. Why was this closed as a dupe of a question about nested classes? – Ben Voigt Sep 18 '14 at 04:45
  • On another note; Is this the correct way for a string enum? – TheGeekZn Sep 18 '14 at 04:58
  • @PeterRitchie When I say "fails" I mean doesn't do what I expect. In this case since the constructor hasn't fired the list is empty. Which causes an exception to be thrown on the `First` call. – Mykroft Sep 18 '14 at 13:02
  • @BoltClock I removed the reference to struct. I was acting under the mistaken impression that I could use const with a struct rather than static readonly which it looks like you can't. – Mykroft Sep 18 '14 at 13:28

2 Answers2

4

Static constructor of some class is called once one of the following conditions is met:

  1. Instance of the class is created;
  2. any static field of the class is accessed.

Since you do not create instances of UseTimeStringEnum neither acess it's static fields within your code, the static constructor is not called.

So the point is: BaseStringEnum does not know of UseTimeStringEnum at the compile time.

I see the only proper solution - we can refer UseTimeStringEnum at the runtime.

I added static constructor to the BaseStringEnum class wich loads and observes all available subclasses with Reflection.

now the static constructor is called.

EDIT: Mykroft pointed there is the way to call a static constructor directly instead of referencing static fields with reflection. So i believe the final code snippet should be

static BaseStringEnum()
{
    var StringEnumTypes = AppDomain.CurrentDomain.GetAssemblies()
          .SelectMany(a => a.GetTypes())
          .Where(type => type.IsSubclassOf(typeof(BaseStringEnum<T>)));

    foreach (var type in StringEnumTypes) type.TypeInitializer.Invoke(null, null);
}
Diligent Key Presser
  • 4,183
  • 4
  • 26
  • 34
1

As others rightly pointed out my constructor isn't being called because the parent class doesn't know the child class. Fortunately in this SO question I found a snippet of code that calls the constructor explicitly.

typeof(T).TypeInitializer.Invoke(null, null); 

This works for my purposes here.

Community
  • 1
  • 1
Mykroft
  • 13,077
  • 13
  • 44
  • 72