在C#泛型类型中初始化静态字段
我从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
并将Values
和ValueOf
向下推入子类来修复此问题,但我希望能够保留超类中的所有复杂性,并将样板文件保持为最小。任何人谁的C#-fu优于我想出一个使子类实例“自动执行”的技巧?
注意:FWIW,这是在Mono中,在Mac OS上。 YM在MS .NET中,在Windows上,MV。
ETA:对于monoglot C#开发者(甚至多语种开发者,他们的经验仅限于以 'C' 语言)想知道WTF我试图做的事:this。 C#枚举关注类型安全问题,但他们仍然缺少其他所有内容。
我想出了这一点 - 不完全是赏心悦目,但做的工作:
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;
}
}
哈哈哈。这是丑陋的罪,但它的工作!做得很好。 – 2011-03-01 05:04:37
@David,IMO,这里可能有问题 - 考虑两个线程同时调用值(第一次) - 第二个线程可能得到错误的计数! – VinayC 2011-03-01 05:12:14
有趣的一点。我不想在每次有人访问某个静态方法时锁定开销......但是也许锁定上面的反射代码,再加上构造函数的内容,会有诀窍吗?必须考虑这一点,看看比赛可能在哪里。 – 2011-03-01 05:16:46
无可否认,我不知道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);
}
}
}
第一次需要向SomeEnum添加行为时,它会停止工作。见例如http://*.com/questions/1376312/whats-the-equivalent-of-javas-enum-in-c – 2011-03-01 04:58:23
我明白了。这是一个有趣的功能。我不确定我会知道如何使用它。 – 2011-03-01 05:05:41
您可以在SomeEnum上使用扩展方法获得简单的行为。当然,这并不适用于所有事情,例如没有运营商超载。 – irritate 2011-03-01 05:17:11
我不喜欢下面的解决方案本身,而是......
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;
}
}
}
我会选择第二个选项去。
如何:
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()在应用程序启动代码。我认为最好把这个简单的要求强加给客户,从而使这个机制可见,而不是指望未来的维护者掌握静态时间初始化的细节。
你不能使这个可靠,废弃这个想法。 – 2011-03-01 04:39:46
当你说“废除想法”时,究竟哪一部分?因为我需要具有行为的枚举类型;这不会消失。 – 2011-03-01 04:59:14
确切地说,枚举中的行为有什么意义?您列出的所有内容都使用Enum类中的静态方法存在。 – Thomas 2011-03-01 05:07:46