Enum列舉型別實戰總結,保證有用!

gui.h發表於2022-04-24

一般在我們開發時如果能使用列舉羅列的,一般都會定義一個列舉型別。將列舉型別作為方法的引數,可以方便的進行呼叫,給我們帶來不少的遍歷,當然有時候它還不如直接用一個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進位制 二進位制
Teacher 1 0x1 0000 0000 0000 0000 0000 0000 0000 0001
Athletes 2 0x2 0000 0000 0000 0000 0000 0000 0000 0010
Singer 4 0x4 0000 0000 0000 0000 0000 0000 0000 0100
Dancer 8 0x8 0000 0000 0000 0000 0000 0000 0000 1000
JobX 16 0x10 0000 0000 0000 0000 0000 0000 0001 0000
JobY 32 0x20 0000 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 商戶id long
merch_name 商戶名 string
certified 已認證? int(0或1)

過了幾個月,隨著產品完善,該表又增加了兩個欄位

欄位 描述 型別
is_vip_merch vip商戶? 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 商戶id long
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了嗎?

相關文章