C#泛型类型中静态字段的初始化

时间:2011-03-01 04:09:45

标签: c# generics initialization

我从this answer了解到C#静态字段初始化程序“在第一次使用该类的静态字段之前执行了”,但仍然产生了我没想到的结果,至少通用类型。

来自Java世界,我错过了我的丰富内容,我认为使用C#更严肃的泛型,我应该能够用最少的样板来复制它们。在这里(删除了一些细节,比如可比性)就是我想出来的:

public class AbstractEnum<T> where T : AbstractEnum<T>
{
    static readonly IDictionary<String, T> nameRegistry = new Dictionary<String, T>();

    readonly String name;

    protected AbstractEnum (String name)
    {
        this.name = name;
        nameRegistry[name] = (T) this;
    }

    public String Name {
        get {
            return name;
        }
    }

    public static T ValueOf(String name) {
        return nameRegistry[name];
    }

    public static IEnumerable<T> Values {
        get {
            return nameRegistry.Values;
        }
    }
}

一些示例子类:

public class SomeEnum : AbstractEnum<SomeEnum> {

    public static readonly SomeEnum V1 = new SomeEnum("V1");
    public static readonly SomeEnum V2 = new SomeEnum("V2");

    SomeEnum(String name) : base(name) {

    }
}

public class OtherEnum : AbstractEnum<OtherEnum> {

    public static readonly OtherEnum V1 = new OtherEnum("V1");
    public static readonly OtherEnum V2 = new OtherEnum("V2");

    OtherEnum(String name) : base(name) {

    }
}

这看起来很好,或多或少的诀窍......除了遵循规范的字母,实际的实例(SomeEnum.V1OtherEnum.V1等)不会被初始化其中至少有一个是明确提到的。基类中的静态字段/方法不计算在内。例如,以下内容:

Console.WriteLine("Count: {0}", SomeEnum.Values.Count());
foreach (SomeEnum e in SomeEnum.Values) {
    Console.WriteLine(e.Name);
}

Count: 0,但如果我添加以下行 -

Console.WriteLine("SomeEnum.V1: " + SomeEnum.V1.Name);

- 甚至之后,我得到:

Count: 2
V1
V2

(顺便说一下,在静态构造函数中初始化实例没有任何区别。)

现在,我可以通过将nameRegistry标记为protected并将ValuesValueOf向下推入子类来解决此问题,但我希望能够保持所有复杂性超类并将样板保持在最低限度。 C#-fu优于我的任何人都能想出一个让子类实例“自动执行”的技巧吗?


注意:FWIW,这是在Mac OS上的Mono中。 YM在MS .NET中,在Windows上,MV。


ETA:对于monoglot C#开发人员(甚至是经验仅限于以'C'开头的语言的多语言开发人员)想知道WTF我正在尝试:this。 C#enums处理类型安全问题,但它们仍然缺少其他所有内容。

4 个答案:

答案 0 :(得分:2)

我想出了这个 - 并不完全令人高兴,但确实做到了:

        public static IEnumerable<T> Values
        {
            get
            {
                if (nameRegistry.Count > 0)
                {
                    return nameRegistry.Values;
                }
                var aField = typeof (T).GetFields(
                                        BindingFlags.Public | BindingFlags.Static)
                                    .FirstOrDefault();

                if (aField != null)
                    aField.GetValue(null);

                return nameRegistry.Values;
            }
        }

编辑这是一个略有不同的版本,应该在评论中解决VinayC的顾虑。问题是这样的:线程A调用Values()。虽然SomeEnum的静态构造函数正在运行,但在它添加V1之后但在它添加V2之前,线程B调用了值。在最初编写的代码中,它将被传递给IEnumerable,它可能只产生V1。因此,如果第二个线程在第一次调用任何特定类型的Values()期间调用,则可能会从Values()获得不正确的结果。

下面的版本使用布尔标志,而不是依赖于nameRegistry中的非零计数。在这个版本中,反射代码仍然可能运行不止一次,但不再可能从Values()得到错误的答案,因为在反射代码完成时,nameRegistry保证完全初始化。

        private static bool _initialized;
        public static IEnumerable<T> Values
        {
            get
            {
                if (_initialized)
                {
                    return nameRegistry.Values;
                }
                var aField = typeof(T).GetFields(
                                            BindingFlags.Public | BindingFlags.Static)
                                        .FirstOrDefault();
                if (aField != null)
                    aField.GetValue(null);
                _initialized = true;
                return nameRegistry.Values;
            }
        }

答案 1 :(得分:1)

不可否认,我不知道RichEnums是什么,但这个C#不能做你想要的吗?

public enum SomeEnum
{
    V1,
    V2
}

class Program
{
    static void Main(string[] args)
    {
        var values = Enum.GetValues(typeof (SomeEnum));
        Console.WriteLine("Count: {0}", values.Length);
        foreach (SomeEnum e in values)
        {
            Console.WriteLine(e);
        }
    }
}

答案 2 :(得分:1)

怎么样:

public class BaseRichEnum 
{
   public static InitializeAll()
   {
      foreach (Type t in Assembly.GetExecutingAssembly().GetTypes())
      {
        if (t.IsClass && !t.IsAbstract && typeof (BaseRichEnum).IsAssignableFrom(t))
        {
          t.GetMethod("Initialize").Invoke(null, null); //might want to use flags on GetMethod
        }
      }
   }
}

public class AbstractEnum<T> : BaseRichEnum where T : AbstractEnum<T>
{
    static readonly IDictionary<String, T> nameRegistry = new Dictionary<String, T>();

    readonly String name;

    protected AbstractEnum (String name)
    {
        this.name = name;
        nameRegistry[name] = (T) this;
    }

    public String Name {
        get {
            return name;
        }
    }

    public static T ValueOf(String name) {
        return nameRegistry[name];
    }

    public static IEnumerable<T> Values {
        get {
            return nameRegistry.Values;
        }
    }    
}

然后:

public class SomeEnum : AbstractEnum<SomeEnum> 
{

        public static readonly SomeEnum V1;
        public static readonly SomeEnum V2;

        public static void Initialize()
        {
          V1 = new SomeEnum("V1");
          V2 = new SomeEnum("V2"); 
        }

        SomeEnum(String name) : base(name) {
        }
    }

然后你必须在应用程序启动代码中调用BaseRichEnum.InitializeAll()。我认为最好将这个简单的要求强加给客户端,从而使机制可见,而不是期望未来的维护者掌握静态时间初始化的微妙之处。

答案 3 :(得分:0)

我不喜欢下面的解决方案,但是......

public class AbstractEnum<T> where T : AbstractEnum<T>
{
   ...
   private static IEnumerable<T> ValuesInternal {
        get {
            return nameRegistry.Values;
        }
   }

   public IEnumerable<T> Values {
     get {
       return ValuesInternal;
     }
   }
}

你必须像SomeEnum.V1.Values一样使用 - 我知道它很糟糕! 另一种涉及一些工作的替代方案是

public class AbstractEnum<T> where T : AbstractEnum<T>
{
   ...
   protected static IEnumerable<T> ValuesInternal {
        get {
            return nameRegistry.Values;
        }
   }
}

public class SomeEnum : AbstractEnum<SomeEnum> {
  ...

  public static IEnumerable<SomeEnum> Values
  {
    get
    {
        return ValuesInternal;
    }

  }
}

我会选择第二种选择。