35

我最近突然想到以下(示例)枚举......

enum Color
{
    Red,
    Green,
    Yellow,
    Blue
}

...可以用一个看似更安全的类代替:

class Color
{
    private Color() { }

    public static readonly Color   Red      = new Color();
    public static readonly Color   Green    = new Color();
    public static readonly Color   Yellow   = new Color();
    public static readonly Color   Blue     = new Color();
}

对于“类型安全”,我的意思是如果是枚举,则以下语句将起作用Color,但如果Color是上述类则不起作用:

var nonsenseColor = (Color)17;    // works if Color is an enum

两个问题:

1)这种模式是否有一个被广泛接受的名称(用类型安全的类替换枚举)?

2)在什么情况下应该使用枚举,什么时候使用一个类更合适?

4

6 回答 6

30

枚举非常适合轻量级状态信息。例如,您的颜色枚举(不包括蓝色)非常适合查询交通灯的状态。真正的颜色以及颜色的整个概念及其所有包袱(alpha、颜色空间等)并不重要,光处于哪个状态无关紧要。另外,稍微改变你的枚举来表示交通灯的状态:

[Flags()]
public enum LightColors
{
    unknown = 0,
    red = 1,
    yellow = 2,
    green = 4,
    green_arrow = 8
}

当前的灯光状态可以设置为:

LightColors c = LightColors.red | LightColors.green_arrow;

并查询为:

if ((c & LightColors.red) == LightColors.red)
{
    //Don't drive
}
else if ((c & LightColors.green_arrow) == LightColors.green_arrow)
{
    //Turn
}

静态类颜色成员将能够支持这种多状态而无需额外的功能。

然而,静态类成员对于常用对象来说非常棒。这些System.Drawing.Color成员是很好的例子,因为它们代表了具有晦涩构造函数的已知颜色(除非您知道您的十六进制颜色)。如果它们被实现为枚举,那么每次您想将值用作颜色时都必须执行以下操作:

colors c = colors.red;
switch (c)
{
    case colors.red:
        return System.Drawing.Color.FromArgb(255, 0, 0);
        break;
    case colors.green:
        return System.Drawing.Color.FromArgb(0,255,0);
        break;
}

因此,如果您有一个枚举并发现您不断地执行 switch/case/if/else/whatever 来派生一个对象,那么您可能想要使用静态类成员。如果您只是查询某物的状态,我会坚持使用枚举。此外,如果您必须以不安全的方式传递数据,枚举可能会比您的对象的序列化版本更好地生存。

编辑: @stakx,我认为您在回复@Anton 的帖子时也偶然发现了一些重要的事情,那就是复杂性,或者更重要的是,它对谁复杂?

从消费者的角度来看,我非常喜欢 System.Drawing.Color 静态类成员,而不是必须编写所有这些。然而,从制作人的角度来看,必须写下所有这些内容会很痛苦。因此,如果其他人要使用您的代码,您可能会通过使用静态类成员为他们省去很多麻烦,即使编写/测试/调试可能需要 10 倍的时间。但是,如果只是您,您可能会发现根据需要使用枚举和转换/转换更容易。

于 2010-01-22T19:58:57.387 回答
3

在此期间我发现了一些东西,如果其他人感兴趣的话:

  • switch块不适用于枚举类。

  • 用户empi提到了上述 enum-as-class 示例与 Java 枚举的相似之处。似乎在 Java 世界中,有一种公认的模式称为Typesafe Enum;显然,这种模式可以追溯到 Joshua Bloch 的《Effective Java 》一书。

于 2010-01-22T19:41:29.677 回答
3

实际上,我在工作中一直在努力解决这样的问题。

它基于John BJon Skeet 的博客文章“Enhanced enums in C#”中发布的示例。

如果没有很多丑陋,我无法正常工作是通过派生基本枚举类并添加派生类型的其他静态字段来扩展枚举。

如果您检查该博客中发布的基础知识,您会注意到派生类将共享一组具有严重类型问题的公共值,并且当某个枚举基类派生在两个不同的其他类中时,它们的值集将在同样的收藏。

我的意思是,您很难创建 DerivedEnumClass1 的变量,并且必须从它的枚举集合中存储一个值,该值实际上是 BaseEnumClass1 类型的变量,没有变通办法。

另一个问题是我们经常使用的 Xml 序列化。我使用两个泛型类作为我们业务对象上枚举变量的数据类型来解决这个问题:一个代表单个枚举值,一个代表一组枚举值,我用它来模拟标志枚举行为。他们处理存储在他们所代表的属性中的实际值的 xml 序列化和反序列化。以这种方式重新创建位标志行为所需要的只是几个运算符重载。

这些是我遇到的一些主要问题。

总而言之,我对最终结果不是很满意,里面有一些难闻的东西,但它现在就可以了。

我们的首席架构师决定仍然尝试让它工作的原因是拥有实际类并用各种行为扩展它们的可能性,无论是通用的还是特定的。到目前为止,我还没有看到很多我无法提供扩展方法的东西,但我们可能会遇到一些问题。

这里没有任何代码,周末我将无法使用它,否则我本可以展示我使用它的地方。虽然不是很漂亮...:o

于 2010-01-22T19:47:14.440 回答
1

当我必须与一些需要一些模糊位标志值的遗留代码互操作时,我会使用类常量。在这种情况下,枚举可能看起来像

public enum Legacy : ushort
{
  SomeFlag1 = 0x0001,
  SomeFlag2 = 0x0002,
  // etc...
}

然后,P/Invoke 的编组可读性较差,因为将枚举转换为适当的值需要进行强制转换。

如果它是 const 类变量,则不需要强制转换。

于 2010-01-22T19:27:02.410 回答
1

两种方法都有效。您应该根据情况进行选择。

我可以添加枚举支持位操作作为标志(甚至有 [Flags] 属性来支持这种语义并从枚举生成漂亮的字符串)。

有一个非常相似的重构,名为用策略模式替换枚举。好吧,在您的情况下,它并不完整,因为您的颜色是被动的并且不像策略。但是,为什么不将其视为特例呢?

于 2010-01-22T20:08:25.003 回答
0

是的,Joshua Bloch 的“Effective Java”的原始版本在 Java 1.5 之前发布,并且对 Java 中的枚举提供了原生支持,它展示了这种Typesafe Enum模式。不幸的是,这本书的最新版本针对的是 Java > 1.5,因此它使用 Java 枚举来代替。

其次,您不能在 Java 中将 int 转换为 enum。

Color nonsenseColor = (Color)17; // compile-error

虽然我不会说其他语言。

于 2010-01-22T19:58:24.297 回答