C# 中的“智能枚举”:如何在枚举中增加行为

2023-05-17 08:37:25  来源:博客园

目录枚举的基本用法回顾枚举常见的设计模式运用介绍智能枚举代码示例业务应用小结枚举的基本用法回顾

以下是一个常见的 C#枚举(enum)的示例:

enum Weekday{    Monday,    Tuesday,    Wednesday,    Thursday,    Friday,    Saturday,    Sunday}
class Program{    static void Main(string[] args)    {        Weekday today = Weekday.Tuesday;        Console.WriteLine("今天是" + today.ToString() + "。");        Console.WriteLine("明天是" + (Weekday)(((int)today + 1) % 7) + "。");        Console.WriteLine("昨天是" + (Weekday)(((int)today + 6) % 7) + "。");    }}

在这个示例中,我们定义了一个名为 Weekday的枚举,其中包括每个星期的日子。然后在 Main方法中,我们将 today变量设置为 Tuesday,并使用 ToString()方法将其转换为字符串。


(资料图片仅供参考)

接下来,我们计算并输出明天和昨天的日子。我们使用强制类型转换将枚举值转换为整数,然后在取模 7 意义下加或减 1 或 6,以便正确地计算出前一天或后一天的日子。

输出结果应该是这样的:

今天是 Tuesday。明天是 Wednesday。昨天是 Monday。
枚举常见的设计模式运用

enum可以应用在许多种设计模式下:

状态模式策略模式工厂模式观察者模式介绍状态模式

状态模式用于根据对象的内部状态来改变其行为。enum可以很好地表示对象的状态,因此它是实现状态模式的常见选择。在 C#中,您可以使用 switch语句来根据不同的 enum值执行不同的操作。

策略模式

策略模式允许您根据运行时条件选择不同的算法或行为。enum可以很好地表示这些条件,因此它是实现策略模式的常见选择。在 C#中,您可以使用 switch语句或 if-else语句来根据不同的 enum值选择不同的算法或行为。

工厂模式

工厂模式允许您使用一个共同的接口来创建不同的对象。enum可以很好地表示这些对象的类型,因此它是实现工厂模式的常见选择。在 C#中,您可以使用 switch语句或 if-else语句来根据不同的 enum值创建不同的对象。

观察者模式

观察者模式用于建立对象之间的松散耦合关系。enum可以很好地表示观察者对象的状态,因此它是实现观察者模式的常见选择。在 C#中,您可以使用 enum来表示观察者对象的状态,并使用委托或事件来通知观察者对象。

智能枚举

什么是智能枚举?智能枚举不是官方的一个称谓,而是作者定义的一个名词。

这种带行为的一种枚举,简单的可以定义为:智能枚举 = 枚举 + 丰富的行为

它由原来的 enum类型(值类型)改变成了 class类型(引用类型),允许您将行为和方法绑定到每个枚举类型上。这意味着您可以在枚举类型上调用方法和属性,就像在类实例上调用它们一样。

智能枚举跟设计模式的意义一样,可以帮助您避免重复的代码,并提高代码的可读性和可维护性。它们还可以使您的代码更加类型安全,因为编译器可以验证您是否使用了正确的枚举值。

代码示例

首先,我们先定义一个抽象的类型,方便后续重用:

public abstract class Enumeration : IEquatable> where TEnum : Enumeration{    private static readonly Dictionary Enumerations = GetEnumerations();    ///     /// 枚举类型    ///     private static readonly Type EnumType = typeof(TEnum);    protected Enumeration(int value, string name)    {        Value = value;        Name = name;    }    ///     /// 名称    ///     public string Name { get; protected init; }    ///     /// 值    ///     public int Value { get; protected init; }    public static TEnum? FromName(string name) => Enumerations.Values.SingleOrDefault(x => x.Name == name);    public static TEnum? FromValue(int value) => Enumerations.TryGetValue(value, out var enumeration) ? enumeration : default;    public bool Equals(Enumeration? other) => other is not null && GetType() == other.GetType() && Value == other.Value;    public override bool Equals(object? obj) => obj is Enumeration other && Equals(other);    public override int GetHashCode() => HashCode.Combine(Value, Name);    private static Dictionary GetEnumerations() => EnumType        .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)        .Where(x => EnumType.IsAssignableFrom(x.FieldType))        .Select(x => (TEnum?)x.GetValue(default)).ToDictionary(x => x?.Value ?? 0);}

先简单解读一下。

这是一个通用的 C#抽象类,用于实现枚举的高级功能。它使用泛型类型 TEnum来表示枚举类型,并继承自 IEquatable>接口,以支持比较操作。

这个抽象类包含了一些常用的枚举操作方法,例如 FromNameFromValue,它们可以通过名称或值来获取枚举值。它还重写了 EqualsGetHashCode方法,以便可以比较两个枚举值是否相等。

该类中的核心方法是 GetEnumerations,它使用反射获取当前枚举类型中的所有字段,并将它们转换为枚举值。在这个过程中,它还会检查字段的类型是否与枚举类型相同,并将值存储在一个字典中,以便以后可以快速地访问它们。

通过继承这个抽象类,您可以轻松地实现自己的枚举类型,并获得许多有用的功能,例如通过名称和值获取枚举值,并支持比较操作。

业务应用

我们通常会将枚举类型这样定义,而在触发业务逻辑时会使用 switch来执行不同的行为,这样就很容易会将逻辑分散在不同的地方。

/// /// 信用卡类型/// public enum CreditCardType{    None = 0,    Standard = 1,    Silver = 2,    Gold = 3,}

那么升级后的智能枚举应该是怎样的呢?它到底智能在哪里?

/// /// 信用卡/// public abstract class CreditCard : Enumeration{    public static readonly CreditCard Gold = new GoldCreditCard();    public static readonly CreditCard Silver = new SilverCreditCard();    public static readonly CreditCard Standard = new StandardCreditCard();    public static readonly CreditCard None = new NoneCreditCard();        private CreditCard(int value, string name) : base(value, name)    {    }    ///     /// 折扣    ///     public abstract double Discount { get; }    ///     /// 金卡    ///     private sealed class GoldCreditCard : CreditCard    {        public GoldCreditCard() : base(3, nameof(Gold))        {        }        public override double Discount => 0.2;    }    private sealed class SilverCreditCard : CreditCard    {        public SilverCreditCard() : base(2, nameof(Silver))        {        }        public override double Discount => 0.1;    }    ///     /// 标准    ///     private sealed class StandardCreditCard : CreditCard    {        public StandardCreditCard() : base(1, nameof(Standard))        {        }        public override double Discount => 0.05;    }    ///     /// 无    ///     private sealed class NoneCreditCard : CreditCard    {        public NoneCreditCard() : base(0, nameof(None))        {        }        public override double Discount => 0;    }}

这里简单解读一下。

这是一个信用卡枚举类型的实现,它继承了之前提到的通用枚举类 Enumeration。其中,GoldCreditCardSilverCreditCardStandardCreditCardNoneCreditCard是四个具体的信用卡子类。

每个子类都重写了父类 CreditCard中的 Discount属性,以表示不同信用卡的折扣率。GoldCreditCard有最高的折扣率,NoneCreditCard没有任何折扣。

CreditCard类中,GoldSilverStandardNone是四个静态实例,表示四种不同的信用卡类型。每个实例都是通过相应的子类创建的,并传入相应的值和名称。值用于标识枚举类型的唯一性,而名称则是该类型的字符串表示。

通过这种方式,我们可以轻松地定义和使用不同类型的信用卡。例如,可以通过 CreditCard.Gold来引用 Gold信用卡的实例,并获取它的折扣率。在需要使用信用卡类型的地方,也可以直接使用 CreditCard类型来表示。

我们平时也可以这样使用,通过 FromNameFromValue

public class SmartEnumsTests{    private readonly ITestOutputHelper _testOutputHelper;    public SmartEnumsTests(ITestOutputHelper testOutputHelper)    {        _testOutputHelper = testOutputHelper;    }    ///     /// 基本用法    ///     [Fact]    public void Use()    {        var standardCard = CreditCard.FromName("Standard");        var standardCard2 = CreditCard.FromValue(1);        Assert.Equal(standardCard, standardCard2);        _testOutputHelper.WriteLine(standardCard!.ToJson());    }}

看完上述的示例代码,智能枚举最明显的好处应该非常直观:就是代码行数增加了亿点点,而不是一点点!

小结

好了,不扯太远了,今天我们就简单总结一下内容吧。

智能枚举 = 枚举 + 丰富的行为

上述示例内容介绍了一个使用 C#枚举类型实现信用卡类型的示例。为了更好地实现该功能,我们创建了一个通用枚举类 Enumeration,并在此基础上实现了 CreditCard类和其四个具体子类,分别表示不同类型的信用卡。

每个子类都包含一个抽象的 Discount属性,表示该类型信用卡的折扣率。而 CreditCard类中的静态实例则表示四种不同的信用卡类型。通过这种方式,我们可以轻松地定义和使用不同类型的信用卡,并在需要使用信用卡类型的地方直接使用 CreditCard类型来表示。