• C#中的的枚举类与枚举在使用上有什么区别?
  • 发布于 2个月前
  • 117 热度
    0 评论
  • 原木风
  • 40 粉丝 32 篇博客
  •   
.NET 8已正式发布,它带来了许多新的东西,其中之一就是Aspire,它可以帮助开发者快速构建微服务应用。如果你想了解如何使用Aspire开发微服务应用,那么你一定不能错过Eshop,这是微软官方提供的微服务开发实践指南,它展示了如何使用Aspire和其他.NET技术构建一个完整的电商应用。

在查看Eshop源码的过程中,我们会发现一个非常有趣的设计,某些实体的类型或状态是通过枚举类来实现的,而不是枚举,比如说订单状态(OrderStatus),为什么要使用这样的设计呢?

我们先来看一下源码:
public class OrderStatus
    : Enumeration
{
    public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
    public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
    public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
    public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
    public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
    public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());
    // 堆代码 duidaima.com
    public OrderStatus(int id, string name)
        : base(id, name)
    {
    }

    public static IEnumerable<OrderStatus> List() =>
        new[] { Submitted, AwaitingValidation, StockConfirmed, Paid, Shipped, Cancelled };

    public static OrderStatus FromName(string name)
    {
        var state = List()
            .SingleOrDefault(s => string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase));

        if (state == null)
        {
            throw new OrderingDomainException($"Possible values for OrderStatus: {string.Join(",", List().Select(s => s.Name))}");
        }

        return state;
    }

    public static OrderStatus From(int id)
    {
        var state = List().SingleOrDefault(s => s.Id == id);

        if (state == null)
        {
            throw new OrderingDomainException($"Possible values for OrderStatus: {string.Join(",", List().Select(s => s.Name))}");
        }

        return state;
    }
}
OrderStatus类继承了Enumeration类,它定义了以下六种订单状态:Submitted(已提交),AwaitingValidation(等待验证),StockConfirmed(库存确认),Paid(已付款),Shipped(已发货),Cancelled(已取消)。每种状态都有一个整数id和一个字符串name,用于构造OrderStatus对象。它还提供了以下三个静态方法:
List():返回一个包含所有订单状态的数组。
FromName(string name):根据name参数查找对应的订单状态,如果不存在则抛出异常。
From(int id):根据id参数查找对应的订单状态,如果不存在则抛出异常。

再来看一下Enumeration类的实现:
public abstract class Enumeration : IComparable
{
    [Required]
    public string Name { get; private set; }
    public int Id { get; private set; }
    protected Enumeration(int id, string name) => (Id, Name) = (id, name);
    public override string ToString() => Name;

    public static IEnumerable<T> GetAll<T>() where T : Enumeration =>
        typeof(T).GetFields(BindingFlags.Public |
                            BindingFlags.Static |
                            BindingFlags.DeclaredOnly)
                    .Select(f => f.GetValue(null))
                    .Cast<T>();

    public override bool Equals(object obj)
    {
        if (obj is not Enumeration otherValue)
        {
            return false;
        }

        var typeMatches = GetType().Equals(obj.GetType());
        var valueMatches = Id.Equals(otherValue.Id);
        return typeMatches && valueMatches;
    }
    public override int GetHashCode() => Id.GetHashCode();

    public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
    {
        var absoluteDifference = Math.Abs(firstValue.Id - secondValue.Id);
        return absoluteDifference;
    }

    public static T FromValue<T>(int value) where T : Enumeration
    {
        var matchingItem = Parse<T, int>(value, "value", item => item.Id == value);
        return matchingItem;
    }

    public static T FromDisplayName<T>(string displayName) where T : Enumeration
    {
        var matchingItem = Parse<T, string>(displayName, "display name", item => item.Name == displayName);
        return matchingItem;
    }

    private static T Parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration
    {
        var matchingItem = GetAll<T>().FirstOrDefault(predicate);
        if (matchingItem == null)
            throw new InvalidOperationException($"'{value}' is not a valid {description} in {typeof(T)}");

        return matchingItem;
    }

    public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id);
}
Enumeration类是一个抽象类,它实现了IComparable接口,有两个属性:Name和Id,分别表示枚举的名称和值。它的构造函数接受两个参数,用于初始化这两个属性。它重写了ToString()方法,返回Name属性的值。它还重写了Equals()和GetHashCode()方法,用于判断两个枚举对象是否相等,以及计算枚举对象的哈希值。它还提供了以下几个静态方法:
GetAll<T>():返回一个包含所有枚举对象的数组,其中T是Enumeration的子类。
AbsoluteDifference(Enumeration firstValue, Enumeration secondValue):返回两个枚举对象的Id属性的绝对差值。
FromValue<T>(int value):根据value参数查找对应的枚举对象,如果不存在则抛出异常。
FromDisplayName<T>(string displayName):根据displayName参数查找对应的枚举对象,如果不存在则抛出异常。
Parse<T, K>(K value, string description, Func<T, bool> predicate):根据value参数和predicate委托查找对应的枚举对象,如果不存在则抛出异常。这是一个私有方法,被FromValue<T>()和FromDisplayName<T>()方法调用。

Enumeration类的设计非常精巧,它提供了一种通用的方式来创建枚举类型,只需要继承它并定义一些静态字段即可。那么,这样的设计到底有哪些好处呢?

其实,通过与枚举进行对比,就可以找到答案,我们来看一看它们的区别:
枚举类可以包含更多的信息,而不仅仅是一个整数值。例如,OrderStatus类除了有Id属性外,还有Name属性,用于表示订单状态的名称。这样可以方便地在界面上显示订单状态,而不需要额外的转换逻辑。而枚举只能表示一个整数值,如果要显示名称,就需要使用switch语句或者字典来映射。

枚举类可以提供更多的方法,而不仅仅是一些基本的操作。例如,OrderStatus类提供了List(),FromName(),From()等方法,用于获取所有的订单状态,或者根据名称或值查找订单状态。而枚举只能使用Enum类的一些静态方法,如Enum.GetValues(),Enum.GetName(),Enum.Parse()等,这些方法的参数和返回值都是object类型,需要进行类型转换,而且容易出错。

枚举类可以实现接口,而枚举不能。例如,Enumeration类实现了IComparable接口,表示它可以进行比较。这样可以方便地对枚举对象进行排序,或者使用一些LINQ方法,如Min(),Max(),OrderBy()等。而枚举不能实现接口,只能使用默认的比较规则,即按照整数值的大小比较。

枚举类可以继承其他类,而枚举不能。例如,OrderStatus类继承了Enumeration类,这样可以方便地复用Enumeration类的一些通用的方法和属性,而不需要重复编写代码。而枚举不能继承其他类,只能继承System.Enum类,这是一个密封的类,不能被修改或扩展。

那么,我们应该如何来选择枚举类和枚举呢?

如果你的枚举类型只是用来表示一些简单的常量,而且不需要进行复杂的操作或逻辑,那么你可以使用枚举,它更简单,更直观,更高效。但是,如果你的枚举类型需要包含一些额外的信息,或者需要提供一些特定的方法,或者需要实现一些接口,或者需要继承一些类,那么你可以使用枚举类,它更灵活,更易于维护。
用户评论