[C#.NET] 二進制運算 Enum of Flags

  • 11490
  • 0
  • C#
  • 2019-09-09

FlagsAttribute 可以讓 Enum 實現二進位的運算(And 、OR、XOR),數值必須以 2 的乘冪(2次方)來定義21,22,23,24

 

這裡我想要用授權碼來說明,授權的設計,一般會在頁面定義使用者能否使用某個功能,功能可以多選,畫面上的設計就像是資料夾權限設定,如下圖:

為了讓程式碼不要有魔術字串、數字出現,我採用 Enum + FlagsAttribute,狀態的保存可以是字串或是數字,不論採用哪種方式都可以使他們變成 Enum。

假使,需要從 DB 閱讀資料,也可以把 Enum 的內容存放到 DB 裡面,正常程序應用程式從 DB 撈出來時,不需要 Join 即可得到詳細的訊息,異常程序直接摸 DB 時,可 Join 得到詳細訊息

 

接下來,看怎麼實作

Enum 標上了 Flags 後就能進行二進制運算 OR、AND、XOR,定義列舉時,要給予它預設狀態,我這裡是用 None=0,表示沒有權限

[Flags]
internal enum EnumPermissionType
{
    None    = 0,
    Read    = 1,
    Add     = 2,
    Edit    = 4,
    Delete  = 8,
    Print   = 16,
    RunFlow = 32,
    Export  = 64,
    All     = Read | Add | Edit | Delete | Print | RunFlow
}

 

使用方式也很簡單

OR 運算,加入 permissions

var permissions = EnumPermissionType.Read | EnumPermissionType.Add;

 

AND 運算:判斷 permissions 是否有項目

var actual = (permissions & EnumPermissionType.Add) == EnumPermissionType.Add;

 

XOR 運算:從 permossions 移除項目,我知道有兩種寫法

var removeAdd = permissions & (EnumPermissionType.All ^ EnumPermissionType.Add);
var removeAdd = permissions & ~EnumPermissionType.Add;

 

列舉,可以變成整數、字串,整數、字串也能倒回去變成列舉,程式碼如下:

[TestMethod]
public void EnumToInt_Test()
{
    var expected  = 18;
    var operation = EnumPermissionType.Add | EnumPermissionType.Print;
    var actual    = operation.ToString();
 
    Assert.AreEqual(expected, actual);
}
 
[TestMethod]
public void EnumToString_Test()
{
    var expected  = "Add, Print";
    var operation = EnumPermissionType.Add | EnumPermissionType.Print;
    var actual    = operation.ToString();
 
    Assert.AreEqual(expected, actual);
}

 

[TestMethod]
public void IntToEnum_Test()
{
    var expected = EnumPermissionType.Add | EnumPermissionType.Print;
    var code     = 18;
    var actual   = (EnumPermissionType) code;
 
    Assert.AreEqual(expected, actual);
}
 
[TestMethod]
public void StringToEnum_Test()
{
    var                expected = EnumPermissionType.Add | EnumPermissionType.Print;
    var                code     = "Add , Print";
    EnumPermissionType actual;
 
    Enum.TryParse(code, out actual);
 
    Assert.AreEqual(expected, actual);
}

 

C# 7 為 TryParse 添加了 out 參數

[TestMethod]
public void StringToEnumTest2()
{
    var expected = EnumPermissionType.Add | EnumPermissionType.Print;
    var code     = "Add , Print";
 
    Enum.TryParse<EnumPermissionType>(code, out var actual);
 
    Assert.AreEqual(expected, actual);
}

 

PS.字串有區分大小寫

 

最後把運算寫成擴充方法,我寫了兩種版本的擴充方法,在還沒有 Enum 的泛型約束前用的是 where TSource : struct, IConvertible 再加上 typeof(TSource).IsEnum 判斷是否為 Enum Type,C# 7.3 就能改用 where TSource : Enum,這樣一來只要是列舉型別都能使用這些擴充方法

public static class EnumerationExtensions
{
    public static TSource Add<TSource>(this TSource source, TSource value) where TSource : struct, IConvertible
    {
        if (!typeof(TSource).IsEnum)
        {
            throw new ArgumentException("T must be an enumerated type");
        }
 
        return (TSource) (object) (Convert.ToInt32(source) | Convert.ToInt32(value));
    }
 
    public static TSource Add1<TSource>(this TSource source, TSource value) where TSource : Enum
    {
        return (TSource) (object) (Convert.ToInt32(source) | Convert.ToInt32(value));
    }
 
    public static bool Has<TSource>(this TSource source, TSource value) where TSource : struct, IConvertible
    {
        if (!typeof(TSource).IsEnum)
        {
            throw new ArgumentException("T must be an enumerated type");
        }
 
        return (Convert.ToInt32(source) & Convert.ToInt32(value)) == Convert.ToInt32(value);
    }
 
    public static bool Has1<TSource>(this TSource source, TSource value) where TSource : Enum
    {
        return (Convert.ToInt32(source) & Convert.ToInt32(value)) == Convert.ToInt32(value);
    }
 
    public static TSource Remove<TSource>(this TSource source, TSource value) where TSource : struct, IConvertible
    {
        if (!typeof(TSource).IsEnum)
        {
            throw new ArgumentException("T must be an enumerated type");
        }
 
        return (TSource) (object) (Convert.ToInt32(source) & ~Convert.ToInt32(value));
    }
 
    public static TSource Remove1<TSource>(this TSource source, TSource value) where TSource : Enum
    {
        return (TSource) (object) (Convert.ToInt32(source) & ~Convert.ToInt32(value));
    }
}

 

測試案例如下

[TestMethod]
public void Add()
{
    var operation = EnumPermissionType.Add
                                      .Add(EnumPermissionType.Read);
    var has = operation.Has(EnumPermissionType.Read);
    Assert.AreEqual(true, has);
}
 
[TestMethod]
public void HasAdd()
{
    var fromDb    = 7;
    var operation = (EnumPermissionType) fromDb;
    var has       = operation.Has(EnumPermissionType.Add);
    Assert.AreEqual(true, has);
}
 
[TestMethod]
public void HasRead()
{
    var fromDb = "Add,Read";
    Enum.TryParse<EnumPermissionType>(fromDb, out var operation);
    var has = operation.Has(EnumPermissionType.Read);
    Assert.AreEqual(true, has);
}
 
[TestMethod]
public void Remove()
{
    var operation = EnumPermissionType.None
                                      .Add(EnumPermissionType.Add)
                                      .Add(EnumPermissionType.Read);
    var after = operation.Remove(EnumPermissionType.Read);
    var has   = after.Has(EnumPermissionType.Read);
    Assert.AreEqual(false, has);
}

 

透過 Enum.GetValues 方法取出所有的列舉成員,再轉成你想要的樣子,要放到物件、資料庫都不是問題

public class AuthorityCode
{
    public int Code { get; set; }
 
    public string Name { get; set; }
 
    public static ICollection<AuthorityCode> ConvertTo<TSource>() where TSource : Enum
    {
        return Enum.GetValues(typeof(TSource))
                   .Cast<TSource>()
                   .Select(p => new AuthorityCode
                   {
                       Code = Convert.ToInt32(p),
                       Name = p.ToString()
                   })
                   .ToList()
            ;
    }
}

文章出自:http://www.dotblogs.com.tw/yc421206/archive/2015/10/20/153618.aspx

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo