2011-03-01 132 views
2

我从this answer了解到,C#静态字段初始值设定项“在第一次使用该类的静态字段之前执行......”,但仍产生我没想到的结果至少使用泛型类型。在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.V1,OtherEnum.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,这是在Mono中,在Mac OS上。 YM在MS .NET中,在Windows上,MV。


ETA:对于monoglot C#开发者(甚至多语种开发者,他们的经验仅限于以 'C' 语言)想知道WTF我试图做的事:this。 C#枚举关注类型安全问题,但他们仍然缺少其他所有内容。

+1

你不能使这个可靠,废弃这个想法。 – 2011-03-01 04:39:46

+0

当你说“废除想法”时,究竟哪一部分?因为我需要具有行为的枚举类型;这不会消失。 – 2011-03-01 04:59:14

+0

确切地说,枚举中的行为有什么意义?您列出的所有内容都使用Enum类中的静态方法存在。 – Thomas 2011-03-01 05:07:46

回答

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()时调用Value(),您可能会得到不正确的结果。

以下版本使用布尔标志而不是依赖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

哈哈哈。这是丑陋的罪,但它的工作!做得很好。 – 2011-03-01 05:04:37

+1

@David,IMO,这里可能有问题 - 考虑两个线程同时调用值(第一次) - 第二个线程可能得到错误的计数! – VinayC 2011-03-01 05:12:14

+0

有趣的一点。我不想在每次有人访问某个静态方法时锁定开销......但是也许锁定上面的反射代码,再加上构造函数的内容,会有诀窍吗?必须考虑这一点,看看比赛可能在哪里。 – 2011-03-01 05:16:46

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

第一次需要向SomeEnum添加行为时,它会停止工作。见例如http://stackoverflow.com/questions/1376312/whats-the-equivalent-of-javas-enum-in-c – 2011-03-01 04:58:23

+0

我明白了。这是一个有趣的功能。我不确定我会知道如何使用它。 – 2011-03-01 05:05:41

+1

您可以在SomeEnum上使用扩展方法获得简单的行为。当然,这并不适用于所有事情,例如没有运营商超载。 – irritate 2011-03-01 05:17:11

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

    } 
} 

我会选择第二个选项去。

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()在应用程序启动代码。我认为最好把这个简单的要求强加给客户,从而使这个机制可见,而不是指望未来的维护者掌握静态时间初始化的细节。