【问题标题】:C# force static fields initializationC# 强制静态字段初始化
【发布时间】:2016-01-11 16:29:21
【问题描述】:

我们目前正在实现某种基于字符串的“可扩展枚举类”。下面只展示这段C#代码的一部分,让问题更容易理解。

如果我运行下面的代码,它会将“BaseValue1”和“BaseValue2”写入控制台。

如果我取消注释 RunClassConstructor 行并运行代码,它还会将“DerivedValue1”和“DerivedValue2”写入控制台。
这就是我想要实现的目标,但我希望在 RunClassConstructor 行的情况下实现它。

我以为DerivedEnum.AllKeys会触发“DerivedValue1”和“DerivedValue2”的创建,但显然不是这样的。

是否有可能实现我想要的,而不强迫这些“枚举类”的用户编写一些魔术代码或进行某种虚拟初始化?

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)
        {
        }
    }
}

【问题讨论】:

标签: c# enums static initialization


【解决方案1】:

只有在第一次访问类时才会调用静态构造函数。

虽然您确实使用了DerivedEnum.AllKeys,但它只是继承自BaseEnum。因此,DerivedEnum 从未被直接引用。

您可以做的一个小技巧实际上是在DerivedEnum 上创建一个new static 属性,该属性从基类返回相同的属性,因此在调用它时将调用派生类的静态构造函数。

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

更新

您也可以使用System.Reflexion 来动态调用它们:

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));
    }
}

您还可以使用此代码来初始化在其他程序集中定义的派生类:

AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(x => x.DefinedTypes)
    .Where(x => typeof(BaseEnum).IsAssignableFrom(x))
    .ToList().ForEach(x => System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(x.TypeHandle));

【讨论】:

  • 谢谢。还有一个解决方案,只需要修改BaseEnum,以便DerivedEnum 的实现者不必注意实现新的静态属性?可能不会,因为BaseEnum 并不知道它的所有派生类...
  • 您也可以使用System.Reflexion 来动态调用它们查看我的更新。
  • 好的,如果所有派生类都在与BaseEnum 类相同的 Dll 中,这将是一个很好的解决方案。不幸的是,在我的代码中并非如此,因为我们库的用户可能会在其他 Dll 中添加自己的派生类。
【解决方案2】:

在 C# 中,静态成员在第一次使用类之前初始化。在您的示例中,您实际上正在使用基类 BaseEnum 的成员并绕过 DerivedEnum,这导致仅初始化 BaseEnum 的静态成员。

您需要在派生类中实现AllKeys 属性。这将确保编译器在您的派生类中使用该属性并初始化它的所有成员。

然后在您的 DerivedEnum 中添加一个新的 AllKeys 属性以覆盖 BaseEnumAllKeys

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

【讨论】:

  • 感谢您的回复。我猜你的意思是AllKeys property,而不是AllKeys field,对吧?
  • @MaDev 正确,感谢您指出这一点。我会更新答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 2017-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多