我从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.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,这是在Mac OS上的Mono中。 YM在MS .NET中,在Windows上,MV。
ETA:对于monoglot C#开发人员(甚至是经验仅限于以'C'开头的语言的多语言开发人员)想知道WTF我正在尝试:this。 C#enums处理类型安全问题,但它们仍然缺少其他所有内容。
答案 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;
}
}
}
我会选择第二种选择。