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