.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类,这是一个密封的类,不能被修改或扩展。
那么,我们应该如何来选择枚举类和枚举呢?
如果你的枚举类型只是用来表示一些简单的常量,而且不需要进行复杂的操作或逻辑,那么你可以使用枚举,它更简单,更直观,更高效。但是,如果你的枚举类型需要包含一些额外的信息,或者需要提供一些特定的方法,或者需要实现一些接口,或者需要继承一些类,那么你可以使用枚举类,它更灵活,更易于维护。