何时以及如何使用枚举类而不是枚举?

13

最近,我工作中的一位开发人员开始在通常适用于枚举类型的地方使用类模式。他使用了类似下面这样的代码:

internal class Suit
{
    public static readonly Suit Hearts = new Suit();
    public static readonly Suit Diamonds = new Suit();
    public static readonly Suit Spades = new Suit();
    public static readonly Suit Clubs = new Suit();
    public static readonly Suit Joker = new Suit();
    private static Suit()
    {

    }
    public static bool IsMatch(Suit lhs, Suit rhs)
    {
        return lhs.Equals(rhs) || (lhs.Equals(Joker) || rhs.Equals(Joker));
    }
}

他的想法是使用一个看起来像枚举但实际上不是枚举的方式,这样可以使与枚举相关的方法(如上面的IsMatch)被包含在枚举本身内。

他称之为“枚举类”,但我之前从未见过。我想知道其优缺点以及如何获取更多信息?

谢谢。

编辑:他描述的另一个优点是能够为枚举添加特定的ToString()实现。


你不能使用枚举来将其类型转换为复杂类型,因此你无法使用枚举来完成这个操作。我猜这就是使用类似于这样的东西的主要原因? - Ant P
我遇到的唯一真正的缺点(除了一些第三方库对此不太满意)是您不能(轻松地)在类型上执行switch/case。但是,我在C#中没有广泛使用这种技术。 - Joachim Isaksson
避免使用switch语句实际上是他的主要卖点。他用它来避免所谓的单片式switch语句。但这一点会限制你即使在想使用时也无法使用。 - Chris Williams
听起来他知道Java枚举... - Marc Gravell
你可以在Jimmy Bogard的文章这里中找到更多关于这个主题的信息。 - Shashank
1
我认为这篇文章有你要找的答案:何时使用枚举,何时用带有静态成员的类替代枚举? - hm1984ir
3个回答

8

枚举在许多情况下都很好用,但在其他情况下则相当不适用。通常我会发现以下问题:

  • 与枚举相关的行为分散在应用程序中
  • 新的枚举值需要进行大量修改
  • 枚举不遵循开放封闭原则

由于与枚举相关的行为已经分散,我们永远无法将其带回到源类型,因为枚举类型不能有任何行为(或者状态)。

换而言之,使用枚举类

每个枚举类型的所有变化都可以向下推入到枚举类中,也可以推入到每个具体子类型中。

枚举在各种场景下工作良好,但在领域模型内部可能会快速失效。枚举类提供了许多相同的可用性,还具有成为行为目的地的额外优点。

不再需要switch语句,因为我可以将可变性和知识推回到模型内部。如果出于某种原因我需要检查特定的枚举类值,则仍然可以选择此选项。这种模式不应该替代所有枚举,但拥有一种备选方案很不错。

可以在这里阅读更多信息。


相关链接:https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types - zwcloud

4
枚举类型的主要优点在于它们基本上是具有一些命名值的整数,因此它们固有的可移植性和可序列化性。枚举类型还可以更快地执行算术和逻辑操作。
当您需要具有额外状态信息的不透明值时,使用枚举类。例如,通用数据访问层可以具有如下接口:
public static class Dal
{
    public static Record Query(Operation operation, Parameters parameters);
}
var result = Dal.Query(Operation.DoSomething, new DoSomethingParameters {...});
对于 Dal 的用户,Operation 只是可用操作的枚举类型,但它可以包含连接字符串、SQL 语句或存储过程以及通用 Dal 需要的任何其他数据。
另一个“常见”的用途是系统中的公共模式(状态或策略)。从用户的角度来看,该模式是不透明的值,但它可能包含对系统实现至关重要的信息或内部功能。以下是一个人为的例子:
public class TheSystem
{
   public SystemMode Mode;
   public void Save()
   {
      Mode.Save();
   }
   public SystemDocument Read()
   {
      return Mode.Read();
   }
}

public abstract class SystemMode
{
   public static SystemMode ReadOnly = new ReadOnlyMode();
   public static SystemMode ReadWrite = new ReadWriteMode();
internal abstract void Save(); internal abstract SystemDocument Read();
private class ReadOnlyMode : SystemMode { internal override void Save() {...} internal override SystemDocument Read() {...} }
private class ReadWriteMode : SystemMode { internal override void Save() {...} internal override SystemDocument Read() {...} } }
TheSystem.Mode = SystemMode.ReadOnly;
我认为仅仅拥有一个 IsMatch 静态方法并不足以不使用简单的枚举类型。在这种情况下,可以使用扩展方法实现类似的功能。

所以它们是一个广为人知的工具,具有其用处,但不应该随处使用。就像大多数事物一样。谢谢你提供的例子。 - Chris Williams

3

枚举类型非常适合轻量级状态信息。例如,您的颜色枚举(不包括蓝色)可用于查询交通信号灯的状态。真实的颜色以及整个颜色概念和所有相关信息(alpha,color space等)并不重要,只需要知道灯处于哪种状态即可。另外,稍微改变一下您的枚举类型,以代表交通信号灯的状态:

[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等语句来获取对象,那么你可能想要使用静态类成员而不是枚举。如果你只查询某个东西的状态,我建议仍然使用枚举。此外,如果你必须以不安全的方式传递数据,枚举可能比序列化版本的对象更加稳定。
参考这个论坛的旧帖子: 何时使用枚举,何时用具有静态成员的类替换它们?

这篇文章非常符合我所寻找的,其中还有来自框架的好例子。谢谢! - Chris Williams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接