4

We are currently implementing some kind of "extensible enum class" based on strings. Only a part of this C# code is displayed below, to make the problem easier to understand.

If I run the code below, it writes "BaseValue1" and "BaseValue2" to the console.

If I uncomment the RunClassConstructor line and run the code, it additionally writes "DerivedValue1" and "DerivedValue2" to the console.
This is what I want to achieve, but I want to achieve it without the RunClassConstructor line.

I thought that DerivedEnum.AllKeys would trigger the creation of "DerivedValue1" and "DerivedValue2", but obviously this is not the case.

Is there a possibility to achieve what I want, without forcing the user of these "enum classes" to write some magic code, or to do some kind of dummy initialization?

using System;
using System.Collections.Generic;

namespace ConsoleApplication
{
    public class Program
    {
        static void Main()
        {
            //System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(DerivedEnum).TypeHandle);

            foreach (var value in DerivedEnum.AllKeys)
            {
                Console.WriteLine(value);
            }
        }
    }

    public class BaseEnum
    {
        private static readonly IDictionary<string, BaseEnum> _dictionary = new Dictionary<string, BaseEnum>();

        public static ICollection<string> AllKeys
        {
            get
            {
                return _dictionary.Keys;
            }
        }

        public static readonly BaseEnum BaseValue1 = new BaseEnum("BaseValue1");
        public static readonly BaseEnum BaseValue2 = new BaseEnum("BaseValue2");

        protected BaseEnum(string value)
        {
            _dictionary[value] = this;
        }
    }

    public class DerivedEnum : BaseEnum
    {
        public static readonly DerivedEnum DerivedValue1 = new DerivedEnum("DerivedValue1");
        public static readonly DerivedEnum DerivedValue2 = new DerivedEnum("DerivedValue2");

        protected DerivedEnum(string value)
            : base(value)
        {
        }
    }
}
MaDev
  • 151
  • 9
  • 1
    [When do static variables get initialized in C#?](http://stackoverflow.com/questions/3965976/when-do-static-variables-get-initialized-in-c), [Is there a way to force static fields to be initialized in C#?](http://stackoverflow.com/questions/2154697/is-there-a-way-to-force-static-fields-to-be-initialized-in-c), so you need to _instantiate_ `DerivedEnum` or access one of _its_ static members. – CodeCaster Jan 11 '16 at 16:32

2 Answers2

2

A static constructor is only invoked when the class is first accessed.

While you did used DerivedEnum.AllKeys, but it is simply inherited from BaseEnum. Therefore DerivedEnum were never directly referenced.

A little hack you could do is actually create a new static property on DerivedEnum that returns the same property from the base class, so when it is called the static constructor of the derived class will be invoked.

public class DerivedEnum : BaseEnum
{
   public new static ICollection<string> AllKeys
   {
       get
       {
           return BaseEnum.AllKeys;
       }
   }
}

UPDATE

You can also use System.Reflexion to dynamically invoke them :

public class BaseEnum
    static BaseEnum()
    {
        // from the types defined in current assembly
        Assembly.GetExecutingAssembly().DefinedTypes
            // for those who are BaseEnum or its derived
            .Where(x => typeof(BaseEnum).IsAssignableFrom(x))
            // invoke their static ctor
            .ToList().ForEach(x => System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(x.TypeHandle));
    }
}

You can also use this code to initialize derived class defined in other assemblies :

AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(x => x.DefinedTypes)
    .Where(x => typeof(BaseEnum).IsAssignableFrom(x))
    .ToList().ForEach(x => System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(x.TypeHandle));
Xiaoy312
  • 14,292
  • 1
  • 32
  • 44
  • Thank you. Is there also a solution where only a modification in the `BaseEnum` is necessary, so that the implementer of `DerivedEnum` doesn't have to take care implementing the new static property? Probably not, because the `BaseEnum` doesn't know all its derived classes... – MaDev Jan 12 '16 at 08:27
  • You can also use `System.Reflexion` to dynamically invoke them See my update. – Xiaoy312 Jan 12 '16 at 22:03
  • OK, this would be a nice solution if all derived classes would be in the same Dll as the `BaseEnum` class. In my code this is unfortunately not the case, because the users of our library might add own derived classes in additional Dlls. – MaDev Jan 13 '16 at 08:25
1

In C#, static members are initialized just before the first usage of the class. In your example, you are actually using a member of the base class BaseEnum and bypassing DerivedEnum which is causing only the static members of BaseEnum to be initialized.

You will need to implement the AllKeys property in your derived class. This will insure that the compiler uses the property in your derived class & initializes all of it's members.

And then add a new AllKeys property in your DerivedEnum to override the AllKeys of the BaseEnum.

new public static ICollection<string> AllKeys
{
    get
    {    
        return BaseEnum.AllKeys;
    }
}
Matt Hensley
  • 908
  • 8
  • 18