为了分享一个古怪的想法,即使没有其他目的,也来试试:
您总是可以实现自己的强枚举
...而且自从引入nameof
运算符以来,您也可以在switch-case语句中使用它们。
(虽然在技术上以前也可以这样做,但很难使这种代码易读和易于重构。)
public struct MyEnum : IEquatable<MyEnum>
{
private readonly string name;
private MyEnum(string name) { name = name; }
public string Name
{
get { return name ?? nameof(Bork); }
}
public static readonly MyEnum Bork;
public static readonly MyEnum Foo;
public static readonly MyEnum Bar;
public static readonly MyEnum Bas;
static MyEnum()
{
FieldInfo[] values = typeof(MyEnum).GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (var value in values)
value.SetValue(null, new MyEnum(value.Name));
}
public override bool Equals(object obj)
{
return obj is MyEnum && Equals((MyEnum)obj);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override string ToString()
{
return Name.ToString();
}
public bool Equals(MyEnum other)
{
return Name.Equals(other.Name);
}
public static bool operator ==(MyEnum left, MyEnum right)
{
return left.Equals(right);
}
public static bool operator !=(MyEnum left, MyEnum right)
{
return !left.Equals(right);
}
}
并这样使用它:
public int Example(MyEnum value)
{
switch(value.Name)
{
default:
return 0;
case nameof(MyEnum.Foo):
return 1;
case nameof(MyEnum.Bar):
return 2;
case nameof(MyEnum.Bas):
return 3;
}
}
你当然会这样调用该方法:
int test = Example(MyEnum.Bar); // 返回2
现在我们可以轻松地获得名称,这基本上只是一个额外的奖励,有些读者可能会指出,这基本上是一个没有空值情况(因为它不是类)的Java枚举。就像在Java中一样,您可以向其添加任何额外的数据和/或属性,例如序数值。
可读性:检查!
智能感知:检查!
重构能力:检查!
是值类型:检查!
真正的枚举:检查!
...
它的性能如何?与本机枚举相比,不行。
你应该使用它吗?嗯......
对于您来说,拥有真正的枚举以便摆脱枚举运行时检查及其伴随的异常有多重要?
我不知道。无法真正回答您,亲爱的读者;每个人都有自己的喜好。
实际上,当我写这个时,我意识到让结构体“包装”一个普通的枚举可能会更清晰。 (静态结构字段和相应的普通枚举通过类似上面的反射相互映射。)只要不将普通枚举用作参数,你就没问题了。
更新:
是的,我花了一整晚测试我的想法,我是对的:现在我在C#中拥有几乎完美的Java风格的枚举。使用简洁,性能得到了改善。
最重要的是:所有令人讨厌的东西都封装在基类中,您自己的具体实现可以像这样干净:
public sealed class Example : Enumeration<Example, Example.Const>, IEnumerationMarker
{
private Example () {}
public static readonly Example Foo = new Example ();
public static readonly Example Bar = new Example ();
public static readonly Example Bas = new Example ();
public enum Const
{
Foo,
Bar,
Bas,
}
}
这是您可以做的事情:
- 您可以添加任何想要的私有字段。
- 您可以添加任何想要的公共非静态字段。
- 您可以添加任何属性和方法。
- 您可以按照自己的意愿设计构造函数,因为:
- 您可以忘记基础构造函数的麻烦。基础构造函数不带参数!
这是您必须做的事情:
- 您的枚举必须是一个密封类。
- 所有构造函数都必须是私有的。
- 您的枚举必须直接继承Enumeration<T, U>并继承空的IEnumerationMarker接口。
- Enumeration<T, U>的第一个泛型类型参数必须是您的枚举类。
- 对于每个公共静态字段,必须存在一个在System.Enum(您指定为Enumeration<T, U>的第二个泛型类型参数)中具有相同名称的值。
- 所有公共静态字段都必须是只读的,并且是您的枚举类型。
- 在类型初始化期间,必须为所有公共静态字段分配唯一的非空值。
目前,在类型初始化时断言了上述每个不变量。稍后可能会尝试进行微调,以查看是否可以在编译时检测其中的一些内容。
要求的理由:
您的枚举必须是密封的,因为如果不是这样,其他不变量会变得更加复杂,而没有明显的好处。
允许公共构造函数没有意义。它是一个枚举类型,基本上是一个单例类型,但实例集合是固定的,而不仅仅是一个。
与第一个原因相同。如果不是这样,反射和一些其他不变量和约束检查将变得混乱。
我们需要这个泛型类型参数,以便使用类型系统来使用高效的编译/Jit时间绑定唯一存储我们的枚举数据。没有哈希表或其他缓慢的机制!理论上可以删除它,但我认为这不值得增加复杂性和性能成本。
这个应该很明显。我们需要这些常量来制作优雅的switch语句。当然,我可以制作一个没有它们的第二个枚举类型;你仍然可以使用之前显示的
nameof
方法进行切换。只是不那么高效。我还在考虑是否应该放宽这个要求。我会试验一下......
您的枚举常量必须是公共静态的,因为明显的原因是只读字段。拥有只读枚举实例意味着所有等式检查简化为引用相等;属性更灵活和冗长,而且这两个都不是枚举实现所需的属性。最后,所有公共静态字段必须是您的枚举类型,因为:它使您的枚举类型保持整洁,减少杂乱;让反射更简单;并且您可以自由地使用属性做任何事情,因此这是一个非常柔性的限制。
这是因为我正在努力将“恶劣的反射魔法”最小化。我不希望我的枚举实现需要完全信任执行。那将严重限制其有用性。更准确地说,在低信任环境中调用私有构造函数或写入只读字段可能会引发安全异常。因此,您的枚举必须在初始化时实例化您的枚举常量-然后我们可以“清洁地”填充这些实例的(内部)基类数据。
无论如何,你怎样使用这些Java风格的枚举?
好的,我现在实现了这些东西:
int ordinal = Example.Bar.Ordinal;
string name = Example.Bas.Name;
int count = Enumeration.Count<Example>();
var value = Example.Foo.Value;
Example[] values;
Enumeration.Values(out values);
foreach (var value in Enumeration.Values<Example>())
Console.WriteLine(value);
public int Switching(Example value)
{
if (value == null)
return -1;
switch (value.Value)
{
case Example.Const.Foo:
return 12345;
case Example.Const.Bar:
return value.GetHasCode();
case Example.Const.Bas:
return value.Ordinal * 42;
default:
return 0;
}
}
抽象枚举类还将为我们实现IEquatable<Example>接口,包括可以在Example实例上使用的==和!=运算符。除了类型初始化期间需要反射之外,一切都很干净且高效。可能会继续实现Java中专门为枚举定义的集合。那么这段代码在哪里呢?在发布之前,我想看看能否进一步简化它,但最后可能会在GitHub的dev分支上放出——除非我找到其他疯狂的项目要工作!^_^
现在已经可以在GitHub上找到了
请查看Enumeration.cs
和Enumeration_T2.cs
。
它们目前是我正在开发的非常wip库的dev分支的一部分。
(目前没有“可发布”内容,并且随时可能出现破坏性更改。)
... 目前该库的其余部分主要是大量样板文件,用于扩展所有数组方法以适用于多秩数组,使多秩数组可与Linq一起使用,并且进行性能优化的ReadOnlyArray包装器(不可变结构体),以安全的方式公开(私有)数组,而无需每次都创建副本。
除了最新dev提交之外的所有内容*都得到了充分的文档记录和IntelliSense友好。
(*java枚举类型仍然在开发中,一旦完成其设计,将进行适当的文档记录。)
enum
的.NET实现,而是switch
语句的工作方式:如果您使用bool
并覆盖true
和false
路径但省略了default
,或者如果您使用byte
并覆盖所有256个可能的路径而没有default
;或者使用short并覆盖所有65536个路径而没有default
等等,您将获得相同的错误。 - LukeH