.net core 使用枚举类而不是枚举类型及应用源生成器

官方建议使用枚举类而不是枚举类型

一、反射enum类型

1. 反射系统enum类型 ConsoleColor

1.1 获取枚举项

  • 使用方法ReflectionEnumeration.GetEnumProvider反射enum
  • IEnumerationProvider是枚举提供器,用于把枚举名或枚举值转化为枚举
  • 可以把IEnumerationProvider对象注入IOC容器方便随时使用
  • 通过Items属性获取所有枚举项,相当于Enum.GetValues方法
  • 通过Names属性获取所有枚举名,相当于Enum.GetNames方法
  • 16个枚举字段转化为16个枚举类(Enumeration)对象
  • 通过TryGet方法获取单个枚举项
  • TryGet方法可以通过枚举名获取,相当于Enum.TryParse方法
  • TryGet方法也可以通过枚举值获取,相当于强转为enum
  • 枚举接口是IEnumeration
  • 枚举类是Enumeration
csharp 复制代码
IEnumerationProvider<Enumeration> provider = ReflectionEnumeration.GetEnumProvider<ConsoleColor>();
Enumeration[] items = provider.Items.ToArray();
Assert.Equal(16, items.Length);
ConsoleColor[] values = Enum.GetValues<ConsoleColor>();
Assert.Equal(16, values.Length);
string[] names = provider.Names.ToArray();
Assert.Equal(16, names.Length);
string[] names0 = Enum.GetNames<ConsoleColor>();
Assert.Equal(16, names0.Length);
// 按枚举名获取
Assert.True(provider.TryGet(nameof(ConsoleColor.Red), out var red));
Assert.Equal(nameof(ConsoleColor.Red), red.Name);
Assert.True(Enum.TryParse<ConsoleColor>(nameof(ConsoleColor.Red), out var red2));
Assert.Equal(ConsoleColor.Red, red2);
// 按枚举值获取
Assert.True(provider.TryGet(10, out var green));
Assert.Equal(nameof(ConsoleColor.Green), green.Name);
var green0 = (ConsoleColor)10;
Assert.Equal(ConsoleColor.Green, green0);
Assert.True(provider.TryGet((long)ConsoleColor.Blue, out var blue));
Assert.Equal(nameof(ConsoleColor.Blue), blue.Name);

1.2 校验枚举项

  • 通过IsDefined校验枚举是否定义
  • 可以通过枚举名校验也可以通过枚举值校验
  • 相当于Enum.IsDefined方法
csharp 复制代码
IEnumerationProvider<Enumeration> provider = ReflectionEnumeration.GetEnumProvider<ConsoleColor>();
Assert.True(provider.IsDefined(nameof(ConsoleColor.Red)));
Assert.True(provider.IsDefined((long)ConsoleColor.Green));
Assert.False(provider.IsDefined("Purple"));
Assert.False(provider.IsDefined(100L));
Assert.True(Enum.IsDefined(typeof(ConsoleColor), nameof(ConsoleColor.Red)));
Assert.True(Enum.IsDefined(typeof(ConsoleColor), (int)ConsoleColor.Green));

2. 反射位标记枚举类型 FileAccess

2.1 获取位标记枚举项

  • 使用方法ReflectionEnumeration.GetFlagEnumProvider反射位标记enum
  • 这里说的位标记枚举类型就是添加了Flags标记的enum
  • IFlagEnumerationProvider是枚举提供器,用于把枚举名或枚举值转化为枚举
  • 可以把IFlagEnumerationProvider对象注入IOC容器方便随时使用
  • IFlagEnumerationProvider包含IEnumerationProvider的所有功能,并支持位运算
  • IFlagEnumerationProvider派生自枚举接口IEnumerationProvider
  • 位标记枚举接口是IFlagEnumeration,派生自枚举接口IEnumeration
  • 位标记枚举类是FlagEnumeration,派生自枚举类Enumeration
  • IFlagEnumeration比IEnumeration多个Flags属性
  • Flags为单位标记,0和复合位标记除外
  • 本Case中ReadWrite就是复合位,等效于 Read | Write,所以Flags比Items少
  • 标记枚举支持直接Get(也可以用TryGet),如果获取不到用Empty填充
csharp 复制代码
IFlagEnumerationProvider<FlagEnumeration> provider = ReflectionEnumeration.GetFlagEnumProvider<FileAccess>();
Assert.Equal(3, provider.Items.Count());
Assert.Equal(2, provider.Flags.Count());
FileAccess[] values = Enum.GetValues<FileAccess>();
Assert.Equal(3, values.Length);
string[] names = provider.Names.ToArray();
Assert.Equal(3, names.Length);
string[] names0 = Enum.GetNames<FileAccess>();
Assert.Equal(3, names0.Length);
// 按枚举名获取
FlagEnumeration read = provider.Get(nameof(FileAccess.Read));
Assert.Equal(nameof(FileAccess.Read), read.Name);
Assert.Empty(read.Description);
var red0 = Enum.Parse<FileAccess>(nameof(FileAccess.Read));
Assert.Equal(FileAccess.Read, red0);
// 按枚举值获取
FlagEnumeration write = provider.Get(2);
Assert.Equal(nameof(FileAccess.Write), write.Name);
Assert.Empty(write.Description);
var write0 = (FileAccess)2;
Assert.Equal(FileAccess.Write, write0);

2.2 Empty属性

  • 由于位标记枚举支持位运算,所以很容易出现枚举值为0的情况
  • 所以定义标记enum尽量定义一个0值字段
  • 0值字段就会转化为Empty属性
  • 如果没有定义0值字段也会生成一个0值枚举类对象
  • 因为0值枚举会参与到位运算逻辑中
csharp 复制代码
IFlagEnumerationProvider<FlagEnumeration> provider = ReflectionEnumeration.GetFlagEnumProvider<FileAccess>();
var empty = provider.Empty;
Assert.Equal(0L, empty.Original);

2.3 与运算

  • And方法用于位标记枚举的与运算,用于平替enum的与(&)运算
  • And支持传多个同类型的位标记枚举
csharp 复制代码
IFlagEnumerationProvider<FlagEnumeration> provider = ReflectionEnumeration.GetFlagEnumProvider<FileAccess>();
var read = provider.Get(nameof(FileAccess.Read));
Assert.NotNull(read);
var write = provider.Get(nameof(FileAccess.Write));
Assert.NotNull(write);
var readWrite = provider.Get(nameof(FileAccess.ReadWrite));
Assert.NotNull(readWrite);
var result = provider.And(read, readWrite);
Assert.Equal(read, result);
var access = provider.And(read, write, readWrite);
Assert.Equal(0L, access.Original);
var result0 = FileAccess.ReadWrite & FileAccess.Read;
Assert.Equal(FileAccess.Read, result0);

2.4 或运算

  • Or方法用于位标记枚举的与运算,用于平替enum的或(|)运算
  • Or支持传多个同类型的位标记枚举
csharp 复制代码
IFlagEnumerationProvider<FlagEnumeration> provider = ReflectionEnumeration.GetFlagEnumProvider<FileAccess>();
var read = provider.Get(nameof(FileAccess.Read));
Assert.NotNull(read);
var write = provider.Get(nameof(FileAccess.Write));
Assert.NotNull(write);
var readWrite = provider.Get(nameof(FileAccess.ReadWrite));
Assert.NotNull(readWrite);
var result = provider.Or(read, write);
Assert.Equal(readWrite, result);
var access = provider.Or(read, write, readWrite);
Assert.Equal(readWrite, access);
Assert.True(access.HasFlag(read));
Assert.True(access.HasFlag((long)FileAccess.Write));
var result0 = FileAccess.Read | FileAccess.Write;
Assert.Equal(FileAccess.ReadWrite, result0);

2.5 HasFlag方法

  • IFlagEnumeration的HasFlag方法用于平替方法enum的HasFlag方法
  • HasFlag支持传枚举、枚举名和枚举值
  • enum的HasFlag只支持枚举
csharp 复制代码
IFlagEnumerationProvider<FlagEnumeration> provider = ReflectionEnumeration.GetFlagEnumProvider<FileAccess>();
FlagEnumeration? read = provider.Get(nameof(FileAccess.Read));
Assert.NotNull(read);
var readWrite = provider.Get(nameof(FileAccess.ReadWrite));
Assert.NotNull(readWrite);
Assert.True(readWrite.HasFlag(read));
Assert.True(readWrite.HasFlag(nameof(FileAccess.Write)));
Assert.True(readWrite.HasFlag((long)FileAccess.Write));
Assert.True(FileAccess.ReadWrite.HasFlag(FileAccess.Read));

2.6 Parse方法

  • Parse支持多个枚举名(逗号分割)和枚举值
  • Parse方法用于平替Enum.TryParse方法
  • Parse与Get有区别
  • Get只能获取定义的枚举项或Empty
  • Parse可以获取位运算后的枚举项(能按位拆解)
csharp 复制代码
IFlagEnumerationProvider<FlagEnumeration> provider = ReflectionEnumeration.GetFlagEnumProvider<FileAccess>(StringComparer.OrdinalIgnoreCase);
var result = provider.Parse("read,write");
FlagEnumeration? readWrite = provider.Get(nameof(FileAccess.ReadWrite));
Assert.NotNull(readWrite);
Assert.Equal(readWrite, result);
var read = provider.Parse((long)FileAccess.Read);
Assert.Equal(nameof(FileAccess.Read), read.Name);
Assert.True(Enum.TryParse<FileAccess>("read,write", true, out var result0));
Assert.Equal(FileAccess.ReadWrite, result0);

3. 枚举别名和备注

3.1 定义含别名和备注的enum

csharp 复制代码
/// <summary>
/// Vip卡类型
/// </summary>
public enum CardType
{
    [Description("银卡")]
    Silver = 1,
    Vip = Silver,
    [Description("金卡")]
    Gold = 2,
    Vip2 = Gold,
    [Description("白金卡")]
    Platinum = 3,
    Vip3 = Platinum,
    [Description("黑金卡")]
    [EnumMember(Value = "SVIP")]
    Black = 4,
    Vip4 = Black,
}

3.2 别名和备注使用示例如下

  • CardType定义了8个字段,解析为4个枚举项
  • 其中Silver、Gold、Platinum和Black为四个枚举项
  • Description标记解析为枚举项的备注(Description属性),作为扩展属性,一般用于展示
  • 可以通过GetByDescription方法按Description获取枚举项
  • Vip、Vip2、Vip3和Vip4等四个解析为别名
  • Black定义的EnumMember标记也解析为别名
  • 另外GetEnumerationProvider有可选参数comparer,传StringComparer.OrdinalIgnoreCase可以实现忽略大小写
  • comparer对Get、IsDefined和TryParse有效
  • comparer参数对前面的GetEnumProvider方法同样有效
csharp 复制代码
IEnumerationProvider<CardType> provider = ReflectionEnumeration.GetEnumerationProvider<CardType>(StringComparer.OrdinalIgnoreCase);
Enumeration[] cardTypes = provider.Items.ToArray();
Assert.Equal(4, cardTypes.Length);
// 按枚举名获取
Assert.True(provider.TryGet(nameof(CardType.Silver), out var silver));
Assert.Equal(nameof(CardType.Silver), silver.Name);
Assert.Equal("银卡", silver.Description);
// 按枚举别名获取
Assert.True(provider.TryGet("Vip", out var vip));
Assert.Equal(silver, vip);
// 按枚举值获取
Enumeration? gold = provider.Get(2);
Assert.NotNull(gold);
Assert.Equal(nameof(CardType.Gold), gold.Name);
Assert.Equal("金卡", gold.Description);
// 用EnumMember标记定义别名
Assert.True(provider.TryGet("svip", out var svip));
// 支持用Description标记定义备注
Enumeration? description = provider.GetByDescription("银卡");
Assert.NotNull(description);
Assert.True(provider.IsDefined(nameof(CardType.Gold)));
Assert.True(provider.IsDefined(nameof(CardType.Vip)));
Assert.True(provider.IsDefined("svip");
var other = new Enumeration("VVip", 100L, "超级会员");
Assert.False(provider.IsDefined(other));

二、自定义枚举类

1. 自定义普通枚举类

1.1 自定义普通枚举类CardType示例

  • CardType继承枚举类Enumeration
  • 构造函数定义为私有的,避免外部生成CardType实例
  • 内部类CardTypeProvider继承EnumerationProvider,其唯一实例是CardType的静态字段Provider
  • CardType封装了一个复杂的打折逻辑
  • 银卡9折(不与其他折扣同享)
  • 金卡最低85折
  • 铂金卡最低8折
  • 黑金卡75折上折
csharp 复制代码
/// <summary>
/// Vip枚举
/// </summary>
public abstract class CardType : Enumeration
{
    private CardType(string name, long original, string description)
        : base(name, original, description)
    {
    }
    #region 配置
    /// <summary>
    /// 打折
    /// </summary>
    /// <param name="price"></param>
    /// <param name="discount"></param>
    /// <returns></returns>
    public abstract bool CheckDiscount(decimal price, ref decimal discount);
    /// <summary>
    /// 银卡
    /// </summary>
    public static readonly CardType Silver = new SilverType();
    /// <summary>
    /// 金卡
    /// </summary>
    public static readonly CardType Gold = new GoldType();
    /// <summary>
    /// 铂金卡
    /// </summary>
    public static readonly CardType Platinum = new PlatinumType();
    /// <summary>
    /// 黑金卡
    /// </summary>
    public static readonly CardType Black = new BlackType();

    /// <inheritdoc cref="Silver" path="/summary"/>
    public static CardType Vip => Silver;
    /// <inheritdoc cref="Gold" path="/summary"/>
    public static CardType Vip2 => Gold;
    /// <inheritdoc cref="Platinum" path="/summary"/>
    public static CardType Vip3 => Platinum;
    /// <inheritdoc cref="Black" path="/summary"/>
    public static CardType Vip4 => Black;
    #endregion
    #region 实现类
    /// <inheritdoc cref="Silver" path="/summary"/>
    private class SilverType()
        : CardType(nameof(Silver), 1, "银卡")
    {
        /// <inheritdoc />
        public override bool CheckDiscount(decimal price, ref decimal discount)
        {
            // 银卡9折(不与其他折扣同享)
            if (discount > 0M)
                return false;
            discount = price * 0.1M;
            return true;
        }
    }
    /// <inheritdoc cref="Gold" path="/summary"/>
    private class GoldType()
        : CardType(nameof(Gold), 2, "金卡")
    {
        /// <inheritdoc />
        public override bool CheckDiscount(decimal price, ref decimal discount)
        {
            // 金卡最低85折
            var discount2 = price * 0.15M;
            if (discount2 > discount)
            {
                discount = discount2;
                return true;
            }
            return false;
        }
    }
    /// <inheritdoc cref="Platinum" path="/summary"/>
    private class PlatinumType()
        : CardType(nameof(Platinum), 3, "铂金卡")
    {
        /// <inheritdoc />
        public override bool CheckDiscount(decimal price, ref decimal discount)
        {
            // 铂金卡最低8折
            var discount2 = price * 0.2M;
            if (discount2 > discount)
            {
                discount = discount2;
                return true;
            }
            return false;
        }
    }
    /// <inheritdoc cref="Black" path="/summary"/>
    private class BlackType()
        : CardType(nameof(Black), 4, "黑金卡")
    {
        /// <inheritdoc />
        public override bool CheckDiscount(decimal price, ref decimal discount)
        {
            // 黑金卡75折上折
            var price2 = price - discount;
            discount += price2 * 0.25M;
            return true;
        }
    }
    #endregion
    #region Provider
    /// <summary>
    /// Vip枚举提供器
    /// </summary>
    public static readonly IEnumerationProvider<CardType> Provider = new CardTypeProvider();
    /// <summary>
    /// Vip枚举提供器
    /// </summary>
    class CardTypeProvider : EnumerationProvider<CardType>
    {
        internal CardTypeProvider()
            : base(
            [Silver, Gold, Platinum, Black], 
            new Dictionary<string, CardType>(StringComparer.OrdinalIgnoreCase)
            {
                { nameof(Silver), Silver},
                { nameof(Gold), Gold},
                { nameof(Platinum), Platinum},
                { nameof(Black), Black},
                { nameof(Vip) , Silver},
                { nameof(Vip2) , Gold},
                { nameof(Vip3) , Platinum},
                { nameof(Vip4) , Black},
            }, 
            new Dictionary<long, CardType>()
            {
                { Silver.Original, Silver},
                { Gold.Original, Gold},
                { Platinum.Original, Platinum},
                { Black.Original, Black}
            }
        )
        {
        }
    }
    #endregion
}

1.2 枚举类演示打折逻辑

  • 模拟业务场景是张三买帽子
  • GetUserByDb和GetProductByDb模拟从存储获取数据
  • 由于会员卡枚举类封装了打折逻辑
  • 使用就非常简单
  • 不需要switch-case来处理不同枚举项
  • 大家可以自行脑补使用enum如何实现同样的逻辑
csharp 复制代码
var user = GetUserByDb("张三");
Assert.True(CardType.Provider.TryGet(user.CardType, out var cardType));
var product = GetProductByDb("帽子");
// 随机领取优惠券
decimal coupon = Random.Shared.Next(0, 20);
// 价格
var price = product.Price;
// 使用优惠券
decimal discount = coupon;
// 享受会员折扣
var state = cardType.CheckDiscount(price, ref discount);
if (state)
    Console.WriteLine("使用会员卡有优惠");
// 计算应付金额
var cost = price - discount;
Assert.True(cost < price);

public User GetUserByDb(string name)
    => new(name, "Silver");
public Product GetProductByDb(string name)
    => new(name, 100M);
public record User(string Name, string CardType);
public record Product(string Name, decimal Price);

2. 自定义位标记枚举类

2.1 自定义位标记枚举类DaysOfWeek示例

  • DaysOfWeek继承标记枚举类FlagEnumeration
  • 构造函数定义为私有的,避免外部生成CardType实例
  • 内部类DaysOfWeekProvider继承FlagEnumerationProvider,其唯一实例是DaysOfWeek的静态字段Provider
  • Sunday、Monday到Saturday表示一周的七天
  • Unknown表示空
csharp 复制代码
/// <summary>
/// 星期几枚举
/// </summary>
public sealed class DaysOfWeek
    : FlagEnumeration
{
    /// <summary>
    /// 这个构造函数是关键,没有这个构造函数会报错
    /// </summary>
    /// <param name="flags"></param>
    /// <param name="original"></param>
    /// <param name="description"></param>
    private DaysOfWeek(ISet<string> flags, long original, string description = "")
        : base(flags, original, description)
    {
    }
    private DaysOfWeek(IEqualityComparer<string> comparer, string name, long original, string description = "")
         : base(comparer, name, original, description)
    {
    }
    private static readonly IEqualityComparer<string> _comparer = StringComparer.OrdinalIgnoreCase;
    #region Days
    /// <summary>
    /// 未知
    /// </summary>
    public static readonly DaysOfWeek Unknown = new(_comparer, nameof(Unknown), 0, "未知");
    /// <summary>
    /// 星期日
    /// </summary>
    public static readonly DaysOfWeek Sunday = new(_comparer, nameof(Sunday), 1, "星期日");
    /// <summary>
    /// 星期一
    /// </summary>
    public static readonly DaysOfWeek Monday = new(_comparer, nameof(Monday), 1 << 1, "星期一");
    /// <summary>
    /// 星期二
    /// </summary>
    public static readonly DaysOfWeek Tuesday = new(_comparer, nameof(Tuesday), 1 << 2, "星期二");
    /// <summary>
    /// 星期三
    /// </summary>
    public static readonly DaysOfWeek Wednesday = new(_comparer, nameof(Wednesday), 1 << 3, "星期三");
    /// <summary>
    /// 星期四
    /// </summary>
    public static readonly DaysOfWeek Thursday = new(_comparer, nameof(Thursday), 1 << 4, "星期四");
    /// <summary>
    /// 星期五
    /// </summary>
    public static readonly DaysOfWeek Friday = new(_comparer, nameof(Friday), 1 << 5, "星期五");
    /// <summary>
    /// 星期六
    /// </summary>
    public static readonly DaysOfWeek Saturday = new(_comparer, nameof(Saturday), 1 << 6, "星期六");
    #endregion
    /// <summary>
    /// 静态实例
    /// </summary>
    public static readonly IFlagEnumerationProvider<DaysOfWeek> Provider = new DaysOfWeekProvider();
    /// <summary>
    /// 星期几枚举提供器
    /// </summary>
    private sealed class DaysOfWeekProvider : FlagEnumerationProvider<DaysOfWeek>
    {
        internal DaysOfWeekProvider()
            : base(
            [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday], 
            new Dictionary<string, DaysOfWeek>(_comparer)
            {
                { nameof(Unknown), Unknown },
                { nameof(Sunday), Sunday},
                { nameof(Monday), Monday},
                { nameof(Tuesday), Tuesday},
                { nameof(Wednesday), Wednesday},
                { nameof(Thursday),Thursday},
                { nameof(Friday), Friday},
                { nameof(Saturday), Saturday}
            }, 
            new Dictionary<long, DaysOfWeek>()
            {
                { Unknown.Original, Unknown },
                { Sunday.Original, Sunday},
                { Monday.Original, Monday},
                { Tuesday.Original, Tuesday},
                { Wednesday.Original, Wednesday},
                { Thursday.Original, Thursday},
                { Friday.Original, Friday},
                { Saturday.Original, Saturday}
            }
        )
        {
        }
        /// <inheritdoc />
        public override DaysOfWeek Empty => Unknown;
        /// <inheritdoc />
        protected override DaysOfWeek CreateFlag(ISet<string> flags, long original, string description = "")
            => new(flags, original, description);
    }
}

2.2 位标记枚举类演示代码

  • 周一到周三员工1排班
  • 周三到周五员工2排班
  • 通过And就可以获取两个员工重叠的值班
  • HasFlag返回true说明当天两人一起值班
  • HasFlag为false说明当天只有一人值班
  • 使用体验和enum差不多,完全可以平替
  • 另外位标记枚举类还封装需要的逻辑或增加字段信息
csharp 复制代码
var provider = DaysOfWeek.Provider;
// 假设员工1周一到周三值班
var duty1 = provider.Or(DaysOfWeek.Monday, DaysOfWeek.Tuesday, DaysOfWeek.Wednesday);
// 假设员工2周三到周五值班
var duty2 = provider.Or(DaysOfWeek.Wednesday, DaysOfWeek.Thursday, DaysOfWeek.Friday);
/// 判断是否有两个员工共同值班
var overlay = provider.And(duty1, duty2);
Assert.True(overlay.HasFlag(DaysOfWeek.Wednesday));
Assert.False(overlay.HasFlag(DaysOfWeek.Tuesday));

三、反射枚举类

  • 自定义枚举类代码中Provider及Provider内部类的代码应该是程序员最不想手写的部分
  • 我们可以用反射来代替

1. 反射普通枚举类

1.1 自定义简单的枚举类CardType

  • CardType除了构造函数,复杂度和enum差不多了
csharp 复制代码
/// <summary>
/// Vip卡类型
/// </summary>
public sealed class CardType : Enumeration
{
    private CardType(string name, long original, string description = "")
        : base(name, original, description)
    {
    }
    /// <summary>
    /// 银卡
    /// </summary>
    public static readonly CardType Silver = new(nameof(Silver), 1, "银卡");
    /// <summary>
    /// 金卡
    /// </summary>
    public static readonly CardType Gold = new(nameof(Gold), 2, "金卡");
    /// <summary>
    /// 铂金卡
    /// </summary>
    public static readonly CardType Platinum = new(nameof(Platinum), 3, "铂金卡");
    /// <summary>
    /// 黑金卡
    /// </summary>
    public static readonly CardType Black = new(nameof(Black), 4, "黑金卡");
    /// <inheritdoc cref="Silver" path="/summary"/>
    public static CardType Vip => Silver;
    /// <inheritdoc cref="Gold" path="/summary"/>
    public static CardType Vip2 => Gold;
    /// <inheritdoc cref="Platinum" path="/summary"/>
    public static CardType Vip3 => Platinum;
    /// <inheritdoc cref="Black" path="/summary"/>
    public static CardType Vip4 => Black;
}

1.2 反射枚举类CardType

  • 使用ReflectionEnumeration.GetEnumerationProvider反射枚举类
csharp 复制代码
IEnumerationProvider<CardType> provider = ReflectionEnumeration.GetEnumerationProvider<CardType>();
CardType[] cardTypes = provider.Items.ToArray();
Assert.Equal(4, cardTypes.Length);
// 按枚举名获取
Assert.True(provider.TryGet("Silver", out var silver));
Assert.Equal("银卡", silver.Description);
// 按别名获取
Assert.True(provider.TryGet("Vip", out var vip));
Assert.Equal(CardType.Silver, silver);
Assert.Equal(silver, vip);
// 获取不存在的枚举时指定默认值,返回默认值
var unknownDefault = provider.Get("xxx", CardType.Silver);
Assert.Equal(CardType.Silver, unknownDefault);
Assert.True(provider.IsDefined(nameof(CardType.Gold)));
Assert.True(provider.IsDefined(CardType.Silver.Original));
Assert.True(provider.IsDefined(CardType.Silver));
Assert.False(provider.IsDefined("SVip"));

2. 反射位标记枚举类

2.1 自定义位标记枚举类DaysOfWeek

  • DaysOfWeek除了构造函数,复杂度和enum差不多了
csharp 复制代码
public class DaysOfWeek
    : FlagEnumeration
{
    /// <summary>
    /// 这个构造函数用于反射,误删会报错
    /// </summary>
    /// <param name="flags"></param>
    /// <param name="original"></param>
    /// <param name="description"></param>
    private DaysOfWeek(ISet<string> flags, long original, string description = "")
        : base(flags, original, description)
    {
    }
    private DaysOfWeek(IEqualityComparer<string> comparer, string name, long original, string description = "")
         : base(comparer, name, original, description)
    {
    }
    private static readonly IEqualityComparer<string> _comparer = StringComparer.OrdinalIgnoreCase;
    #region Days
    /// <summary>
    /// 星期日
    /// </summary>
    public static readonly DaysOfWeek Sunday = new(_comparer, "Sunday", 1, "星期日");
    /// <summary>
    /// 星期一
    /// </summary>
    public static readonly DaysOfWeek Monday = new(_comparer, "Monday", 1 << 1, "星期一");
    /// <summary>
    /// 星期二
    /// </summary>
    public static readonly DaysOfWeek Tuesday = new(_comparer, "Tuesday", 1 << 2, "星期二");
    /// <summary>
    /// 星期三
    /// </summary>
    public static readonly DaysOfWeek Wednesday = new(_comparer, "Wednesday", 1 << 3, "星期三");
    /// <summary>
    /// 星期四
    /// </summary>
    public static readonly DaysOfWeek Thursday = new(_comparer, "Thursday", 1 << 4, "星期四");
    /// <summary>
    /// 星期五
    /// </summary>
    public static readonly DaysOfWeek Friday = new(_comparer, "Friday", 1 << 5, "星期五");
    /// <summary>
    /// 星期六
    /// </summary>
    public static readonly DaysOfWeek Saturday = new(_comparer, "Saturday", 1 << 6, "星期六");
    #endregion
}

2.2 反射位标记枚举类DaysOfWeek

  • 使用ReflectionEnumeration.GetEnumerationProvider反射枚举类
csharp 复制代码
IFlagEnumerationProvider<DaysOfWeek> provider = ReflectionEnumeration.GetFlagEnumerationProvider<DaysOfWeek>();
DaysOfWeek[] cardTypes = provider.Items.ToArray();
Assert.Equal(7, cardTypes.Length);
Assert.True(provider.IsDefined(DaysOfWeek.Saturday));
Assert.True(provider.IsDefined(DaysOfWeek.Saturday.Original));
Assert.False(provider.IsDefined("DayEight"));
var other = new DaysOfWeek(StringComparer.Ordinal, "DayEight", 1 << 7, "星期八");
Assert.False(provider.IsDefined(other));
var weekend = provider.Or(DaysOfWeek.Saturday, DaysOfWeek.Sunday);
Assert.True(weekend.HasFlag(DaysOfWeek.Saturday));
Assert.False(weekend.HasFlag(DaysOfWeek.Monday));
// 假设员工一周一和周二值班
var duty1 = provider.Or(DaysOfWeek.Monday, DaysOfWeek.Tuesday);
// 假设员工二周二和周三值班
var duty2 = provider.Or(DaysOfWeek.Tuesday, DaysOfWeek.Wednesday);
/// 获取两员工共同值班的时间
var overlay = provider.And(duty1, duty2);
Assert.Equal(DaysOfWeek.Tuesday, overlay);

四、基于enum类型生成枚举类

1. 配置代码

  • 枚举类型CardType作为枚举类CardEnumeration的原型
  • 使用源生成器GenerateEnumeration生成CardEnumeration
csharp 复制代码
using Hand.Enumerations;
using Hand.Primitives;
using System.ComponentModel;
using System.Runtime.Serialization;

namespace GenerateEnumerationTests;

/// <summary>
/// Vip卡类型
/// </summary>
public enum CardType
{
    /// <summary>
    /// 银卡
    /// </summary>
    [Description("银卡")]
    Silver = 1,
    Vip = Silver,
    /// <summary>
    /// 金卡
    /// </summary>
    [Description("金卡")]
    Gold = 2,
    Vip2 = Gold,
    /// <summary>
    /// 白金卡
    /// </summary>
    [Description("白金卡")]
    Platinum = 3,
    Vip3 = Platinum,
    /// <summary>
    /// 黑金卡
    /// </summary>
    [Description("黑金卡")]
    [EnumMember(Value = "SVIP")]
    Black = 4,
    Vip4 = Black,
}

[GenerateEnumeration<CardType>]
public partial class CardEnumeration;

2. 生成后的代码

  • CardEnumeration为枚举类
  • 静态字段Provider用来把枚举名和枚举值转化为CardEnumeration
  • 其他静态字段和属性用来平替CardType的枚举字段
  • EnumMember作为别名
  • Description和注释作为备注
csharp 复制代码
// <auto-generated/>

using Hand.Enumerations;
using System.Collections.Generic;

namespace GenerateEnumerationTests;
partial class CardEnumeration : Enumeration
{
    ///<summary>
    ///银卡
    ///</summary>
    public static readonly CardEnumeration Silver = new("Silver", 1L, "银卡");
    ///<summary>
    ///金卡
    ///</summary>
    public static readonly CardEnumeration Gold = new("Gold", 2L, "金卡");
    ///<summary>
    ///白金卡
    ///</summary>
    public static readonly CardEnumeration Platinum = new("Platinum", 3L, "白金卡");
    ///<summary>
    ///黑金卡
    ///</summary>
    public static readonly CardEnumeration Black = new("Black", 4L, "黑金卡");
    ///<summary>
    ///Silver
    ///</summary>
    public static CardEnumeration Vip => Silver;
    ///<summary>
    ///Gold
    ///</summary>
    public static CardEnumeration Vip2 => Gold;
    ///<summary>
    ///Platinum
    ///</summary>
    public static CardEnumeration Vip3 => Platinum;
    ///<summary>
    ///Black
    ///</summary>
    public static CardEnumeration SVIP => Black;
    ///<summary>
    ///Black
    ///</summary>
    public static CardEnumeration Vip4 => Black;

    public static readonly IEnumerationProvider<CardEnumeration> Provider = new CardEnumerationProvider();
    class CardEnumerationProvider : EnumerationProvider<CardEnumeration>
    {
        internal CardEnumerationProvider() : base([Silver, Gold, Platinum, Black], new Dictionary<string, CardEnumeration> { { "Silver", Silver }, { "Gold", Gold }, { "Platinum", Platinum }, { "Black", Black }, { "Vip", Silver }, { "Vip2", Gold }, { "Vip3", Platinum }, { "SVIP", Black }, { "Vip4", Black } }, new Dictionary<string, CardEnumeration> { { 1L, Silver }, { 2L, Gold }, { 3L, Platinum }, { 4L, Black } })
        {
        }
    }
}

五、源生成器对枚举类补充代码

1. 配置代码

  • Status是枚举类
  • StatusProvider作为Status的枚举提供器用于解析枚举
  • 如果是位标记枚举务必一个枚举项的枚举值定为0,原生器不方便解析枚举值,默认把第一个作为值为0的空枚举
csharp 复制代码
using Hand.Enumerations;
using Hand.Primitives;

namespace GenerateEnumerationTests;

public class Status : Enumeration
{
    private Status(string name, int value) : base(name, value, name)
    {
    }
    public static readonly Status Open = new("Open", 1);
    public static readonly Status Closed = new("Closed", 2);
}
[GenerateEnumeration<Status>]
public partial class StatusProvider;

2. 生成后的代码

csharp 复制代码
// <auto-generated/>

using Hand.Enumerations;
using System.Collections.Generic;

namespace GenerateEnumerationTests;
partial class StatusProvider : EnumerationProvider<Status>
{
    internal StatusProvider() : base(new Dictionary<string, Status> { { "Open", Status.Open }, { "Closed", Status.Closed } })
    {
    }

    public static readonly StatusProvider Instance = new();
}

六、示例说明

1. 示例执行说明

  • 以上代码执行依赖开源项目Hand.Enumeration、Hand.GenerateEnumeration和Hand.ReflectionEnumeration
  • Hand.Enumeration --version 0.3.2.7-alpha
  • Hand.GenerateEnumeration --version 0.3.1-alpha
  • Hand.ReflectionEnumeration --version 0.3.2.7-alpha

2. Hand.Enumerationn和Hand.ReflectionEnumeration示例代码及开源源码

3. Hand.GenerateEnumeration示例代码及开源源码

七、最佳实践总结

1. 简单枚举以枚举类型为原型

  • 使用源生成器生成枚举类及枚举提供器

2. 自定义枚举类扩展枚举属性和功能

  • 使用源生成器生成枚举提供器
  • 枚举类修改后枚举提供器会自动更新
相关推荐
xiangji2 个月前
C#.NET源生成器如何处理XML注释文档
roslyn·sourcegenerator·syntaxtree·easysyntax·源生成器
xiangji2 个月前
C# .NET源生成器如何处理Attribute
源生成器
最笨的羊羊1 年前
Flink CDC系列之:Kafka 分区策略枚举类PartitionStrategy
partition·flink cdc系列·strategy·枚举类·kafka 分区策略
tekin2 年前
Python 元类:编程魔法的深度揭秘与实战应用
python·枚举类·抽象基类·python 元类·元类原理·自定义元类·元类应用场景
微小冷3 年前
Rust结构体和枚举类
rust·match·结构体·枚举类·成员方法·关联函数