Enum枚举类型实战总结,保证有用!

1,254 阅读6分钟

一般在我们开发时如果能使用枚举罗列的,一般都会定义一个枚举类型。将枚举类型作为方法的参数,可以方便的进行调用,给我们带来不少的遍历,当然有时候它还不如直接用一个int类型带来,带来一定灵活性。但只要能满足业务咱们就怎么方便怎么来吧。

基本使用

我们业务中会经常遇到订单状态的枚举,它罗列出了所有订单状态的可能值,下面是我刚刚编的一个订单状态枚举

public enum OrderStatus
{
    /// <summary>
    /// 未支付
    /// </summary>
    WaitPay = 0,
 
    /// <summary>
    /// 已支付
    /// </summary>
    Payed = 1,
 
    /// <summary>
    /// 已退款
    /// </summary>
    Refund = 2,
 
    /// <summary>
    /// 已关闭
    /// </summary>
    Closed = 3,
}

我们都知道C# 枚举成员的类型默认是 int 类型,通过继承可以声明枚举成员为其它类型,例如

public enum OrderStatus: byte
{
    /// <summary>
    /// 未支付
    /// </summary>
    WaitPay = 0,
 
    /// <summary>
    /// 已支付
    /// </summary>
    Payed = 1,
 
    /// <summary>
    /// 已退款
    /// </summary>
    Refund = 2,
 
    /// <summary>
    /// 已关闭
    /// </summary>
    Closed = 3,
}

还真是“听君一席话,如听一席话”,别,干货这就来。

搭配Description使用

我相信大部分人都知道这么玩

public enum OrderStatus
{
    [Description("未支付")]
    WaitPay = 0,
 
    [Description("已支付")] 
    Payed = 1,
 
    [Description("已退款")] 
    Refund = 2,
 
    [Description("已关闭")]
    Closed = 3,
}

写一个扩展方法,用于获取Description的描述信息。

public static class EnumExtensions
{
    public static string GetDescription(this Enum obj)
    {
        object[]? array = obj.GetType().GetField(obj.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), inherit: true);
        if (array != null)
        {
            var attr = array.FirstOrDefault(x => x is DescriptionAttribute);
            if (attr != null)
            {
                return ((DescriptionAttribute)attr).Description;
            }
            
        }
 
        return string.Empty;
    }
}

然后我们就可以很方便的获取枚举的描述信息了,这个好像有点用。 image.png

搭配Flag属性使用

在我们对枚举进行或运算时,如

internal enum Jod
{
    /// <summary>
    /// 老师
    /// </summary>
    Teacher = 1,
 
    /// <summary>
    /// 运动员
    /// </summary>
    Athletes = 2
}

某人既是老师,又是国家运动员,我们对枚举进行或运算后由于结果是3. image.png

这是因为Jod中不存在这样的一个值为3的枚举,所以会输出3;这一般情况下并不是我们想要的,此时我们只需要对这个枚举加上一个属性[Flags]

[Flags]
internal enum Jod
{
    /// <summary>
    /// 老师
    /// </summary>
    Teacher = 1,
 
    /// <summary>
    /// 运动员
    /// </summary>
    Athletes = 2
}

image.png

讲道理,这个有用,但我很少用~

位运算

上文中一共提到了两个枚举类型OrderStatusJod,他们正好分别对应互斥型和非互斥型,订单的状态某一时刻只能有一种,而工作可以同时有多个(举例可能不恰当,知道意思即可)。

枚举类型的值不是所有的情况下都是加单的对新增的成员加1,比如Jod枚举随着业务增加,又新增了歌手和舞者

[Flags]
internal enum Jod
{
    /// <summary>
    /// 老师
    /// </summary>
    Teacher = 1,
 
    /// <summary>
    /// 运动员
    /// </summary>
    Athletes = 2,
 
    /// <summary>
    /// 歌手
    /// </summary>
    Singer = 3,
 
    /// <summary>
    /// 舞者
    /// </summary>
    Dancer = 4
}

如果你觉得上面的枚举没问题,那问题就严重了,由于对于非互斥关系的枚举,我们可以很方便的进行或运算来表示同时兼多种枚举值的情况。可以通过与运算检查一个枚举值是否包含某个值,可以通过异或同或操作进行更为有趣的操作,为了能够进行优雅的位运算,枚举值的分配则不能按照上面的12345累加1进行,而是要按照下例:

[Flags]
internal enum Jod
{
    /// <summary>
    /// 老师
    /// </summary>
    Teacher = 1,
 
    /// <summary>
    /// 运动员
    /// </summary>
    Athletes = 2,
 
    /// <summary>
    /// 歌手
    /// </summary>
    Singer = 4,
 
    /// <summary>
    /// 舞者
    /// </summary>
    Dancer = 8,
 
    Jobx = 0x10,
 
    JobY = 0x20,
 
    JobZ = 0x40,
    ...
}

我们知道int转成二进制是由0和1,一共32位组成的,位运算正是二进制运算的方法,上面的枚举继承自int,如果将32位二进制数的每一位表示一种职业,那么一共可以表示32个职业。对应关系如下

枚举值十进制16进制二进制
Teacher10x10000 0000 0000 0000 0000 0000 0000 0001
Athletes20x20000 0000 0000 0000 0000 0000 0000 0010
Singer40x40000 0000 0000 0000 0000 0000 0000 0100
Dancer80x80000 0000 0000 0000 0000 0000 0000 1000
JobX160x100000 0000 0000 0000 0000 0000 0001 0000
JobY320x200000 0000 0000 0000 0000 0000 0010 0000
............

常用操作

// 1.基本的或运算,表示同时有多种枚举值的情况
var jobs = Jod.Teacher | Jod.Athletes;
 
// 2.判断某个人的职业中是否有Athletes
if ((jobs & Jod.Athletes) == Jod.Athletes)
{
    // 是运动员
}

我们可以将enum的数值存到数据库,写sql时也可以使用位运算的,从数据库中查到的数据转成Model后在业务代码中就可以优雅的使用位运算进行判断了。

数据库设计中的妙用

最初知道Flags这个属性的时候就在想,他为什么叫Flags?直到我遇到下面这样的业务场景(瞎编的,非公司实际业务场景,但可以说明问题)。

一般场景

例如我们电商平台管理的商户,最开始我们会有个商户表merch,字段如下

字段描述类型
merch_id商户idlong
merch_name商户名string
certified已认证?int(0或1)

过了几个月,随着产品完善,该表又增加了两个字段

字段描述类型
is_vip_merchvip商户?int(0或1)
is_defect_free商品上架免检int(0或1)

又过了几个月,又增加了几个字段

字段描述类型
is_frozen是否冻结int(0或1)
is_mvp是否金牌商户int(0或1)

优化

每次新的需要来了,就需要增加字段,最后这张表,光这种标识字段就好快10来个了,这样维护起来太难受了吧。如果我说可以将这10来个标识字段用一个字段搞定,你会不会惊讶!这里是跟新手说的,大佬们自然知道我下面要怎么干了。

我将上面的表字段进行了优化,由7个字段,缩减到3个字段。

字段描述类型
merch_id商户idlong
merch_name商户名string
merch_flags各种商户标识int

并给这个merch_flags定义了一个枚举

[Flags]
public enum MerchFlags
{
    /// <summary>
    /// 已认证?	
    /// </summary>
    certified = 1,
    /// <summary>
    /// vip商户?
    /// </summary>
    is_vip_merc = 2,
    /// <summary>
    /// 商品上架免检
    /// </summary>
    is_defect_free = 4,
    /// <summary>
    /// 是否冻结
    /// </summary>
    s_frozen = 8,
    /// <summary>
    /// 是否金牌商户
    /// </summary>
    is_mvp = 0x10,
    
    // ...继续新增各种标志位
}

到这里应该明白这是要干嘛了吧,以后再来新的业务需要加标志字段,直接在枚举MerchFlags加一个就行了,数据库不需要加字段了。int类型的枚举可以给你32个标志可以用,long可以存64个,一般场景是够用了。

思考一个问题

你知道Flags属性为什么叫Flags了吗?