【C#】Enum Flags 屬性用法

平常定義 Enum 這樣寫

public enum CarBrand
{
    None,
    Tesla,
    Honda,
    Ferrari,
    Porsche
}

這個寫法沒有定義明確的值,但實際上編譯器會幫妳自動給從 0 開始的值

Console.WriteLine($"{CarBrand.None} = {(int)CarBrand.None}");       //None = 0
Console.WriteLine($"{CarBrand.Tesla} = {(int)CarBrand.Tesla}");     //Tesla = 1
Console.WriteLine($"{CarBrand.Honda}  = {(int)CarBrand.Honda}");    //Honda = 2
Console.WriteLine($"{CarBrand.Ferrari}  = {(int)CarBrand.Ferrari}");//Ferrari = 3
Console.WriteLine($"{CarBrand.Porsche}  = {(int)CarBrand.Porsche}");//Porsche = 4

要不要定義明確的 value 就依照使用情境判斷即可。

通常將值存到 DB 裡面,我都會定義明確的值,以免 enum 順序被其他人改掉採到坑裡。


假設原本 DB 有個欄位是紀錄最愛的汽車品牌,那把值直接填進去沒問題。
我愛 Tesla,我就填 1; 我愛 Honda,我就填 2~

但小孩才做選擇!我愛 Tesla, Honda, Ferrari!
這時候我們就會選擇使用 bit 處裡

以前土炮的寫法,定義 enum 值是次方

public enum CarBrand
{
    None = 0,   //2^0
    Tesla = 1,  //2^1
    Honda = 2,  //2^2
    Ferrari = 3,//2^3
    Porsche = 4 //2^4
}

我愛 Tesla => 存到 DB 的值為 2^1=2
我愛 Tesla, Honda => 存到 DB 的值為 2^1+2^2=6

使用方式如下

public class BitHelper
{
    public static List<int> GetBitList(int value)
    {
        List<int> bitList = new List<int>();
        double @base = 2;
        double residualValue = Convert.ToDouble(value);
        while (residualValue > 0)
        {
            double logValue = Math.Log(residualValue, @base);
            int exponent = Convert.ToInt32(Math.Floor(logValue));
            bitList.Add(exponent);
            residualValue -= Math.Pow(@base, exponent);
        }
        return bitList;
    }

    public static bool IsContainBit(List<int> bitList, Enum bit)
    {
        return bitList.Contains(Convert.ToInt32(bit));
    }
}

//get value
List<int> bitList = BitHelper.GetBitList(DbCarBrandValue);
bool hasTesla = BitHelper.IsContainBit(bitList, CarBrand.Tesla);

//set value
List<int> bitList = BitHelper.GetBitList(DbCarBrandValue);
int updateValue;
if (!hasTesla && BitHelper.isContainBit(bitList, CarBrand.Tesla))
  updateValue = DbCarBrandValue - (int)Math.Pow(2, (int)CarBrand.Tesla);
else if (hasTesla == true && !BitHelper.isContainBit(bitList, CarBrand.Tesla))
  updateValue = DbCarBrandValue + (int)Math.Pow(2, (int)CarBrand.Tesla);

認識了 Flags 之後,寫法方便很多,有三種定義 enum 的方式,寫法如下

//寫法一
[Flags]
public enum CarBrand
{
    None = 0,
    Tesla = 1,
    Honda = 2,
    Ferrari = 4,
    Porsche = 8
}

//寫法二
[Flags]
public enum CarBrand
{
    None = 0,        //0
    Tesla = 1 << 0,  //1
    Honda = 1 << 1,  //2
    Ferrari = 1 << 2,//4
    Porsche = 1 << 3 //8
}

//寫法三
[Flags]
public enum CarBrand
{
    None = 0,             //0
    Tesla = 1,            //1
    Honda = Tesla << 1,   //2
    Ferrari = Honda << 1, //4
    Porsche = Ferrari << 1//8
}

其中 << 是左移運算符
1 << 1 表示將十進制的 1 往左移 1 位 => 0010 => 2
1 << 2 表示將十進制的 1 往左移 2 位 => 0100 => 4

//get value
int dbValue = 3;//Tesla, Honda
CarBrand carBrand = (CarBrand)dbValue;
Console.WriteLine($"has {CarBrand.None}  = {carBrand.HasFlag(CarBrand.None)}");      //has None  = True
Console.WriteLine($"has {CarBrand.Tesla}  = {carBrand.HasFlag(CarBrand.Tesla)}");    //has Tesla  = True
Console.WriteLine($"has {CarBrand.Honda}  = {carBrand.HasFlag(CarBrand.Honda)}");    //has Honda  = True
Console.WriteLine($"has {CarBrand.Ferrari}  = {carBrand.HasFlag(CarBrand.Ferrari)}");//has Ferrari  = False
Console.WriteLine($"has {CarBrand.Porsche}  = {carBrand.HasFlag(CarBrand.Porsche)}");//has Porsche  = False

//set value
Console.WriteLine($"{CarBrand.Tesla} + {CarBrand.Honda}  = {CarBrand.Tesla | CarBrand.Honda}");//Tesla + Honda  = Tesla, Honda
Console.WriteLine($"{CarBrand.Tesla} + {CarBrand.Honda}  = {(int)(CarBrand.Tesla | CarBrand.Honda)}");//Tesla + Honda  = 3

//add value
int dbValue = 3;//Tesla, Honda
CarBrand carBrand = (CarBrand)dbValue;
Console.WriteLine($"{carBrand} + {CarBrand.Honda}  = {carBrand | CarBrand.Honda}");//Tesla, Honda + Honda  = Tesla, Honda
//0011 | 0010 => 0011

//minus value 
int dbValue = 7;//Tesla, Honda, Ferrari
CarBrand carBrand = (CarBrand)dbValue;
Console.WriteLine($"{carBrand} - {CarBrand.Honda}  = {carBrand &~ CarBrand.Honda}");//Tesla, Honda, Ferrari - Honda  = Tesla, Ferrari
//0111 & 1101 => 0101

「|」是將二進制每個位進行運算,有 1 則得 1
0010 | 0100 => 0110
0110 | 0101 => 0111

「&」是將二進制每個位進行運算,1 & 1 才得 1

0010 & 0100 => 0000
0110 & 0101 => 0100

「~」是將二進制每個位進行運算,0 則變 1;1 則變 0
0010 => 1101
0100 => 1011

參考資料:
https://learn.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=net-7.0

分類: C#

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *