C# Flags Enum迭代與篩選
使用列舉可避免魔術數字,增加程式可讀性。而C#有Flags屬性,將列舉的值定義為2的n次方,即可使用and、or、xor等運算達到列舉狀態疊加、移除等操作。以下定義一個列舉進行測試
[Flags]
public enum TestEnum
{
undefined = 0,
A = 1,
B = 1 << 1,
C = 1 << 2,
D = 1 << 3,
E = 1 << 4,
BCE = B | C | E
}
接著進行列舉疊加,可以發現TestEnum.A、TestEnum.B列舉疊加後在ToString時產生的字串變成A,B,而TestEnum.BCE卻沒有顯示為B,C,E
如果現在有一個TestEnum的列舉,如何找出它是哪些列舉疊加而成的? 就如同TestEnum.BCE,我們應該如何知道它是B、C、E?
當然可以透過HasFlag判斷,只要遵循列舉的值定義為2的n次方,結果就會正確,只是一個個 if 的判斷看起來不太優雅
這時可以使用Enum.GetValues方法,之後再進行轉型就可以得到該列舉所有成員
既然已取得所有成員,要篩選HasFlag就容易了
// 上面那段篩選改寫成泛型,因為這邊沒有使用泛型約束所以還要做轉型
IEnumerable<T> GetFlags<T>(T theEnum)
{
return Enum.GetValues(typeof(T)).Cast<T>().Where(x => (theEnum as Enum).HasFlag(x as Enum));
}
此時我們又發現TestEnum.undefined與TestEnum.BCE也在其中,邏輯上來說確實是符合了HasFlag,但是如果我們只想要B、C、E這些成員時又該如何篩選?
列舉定義的值在疊加前都是2的n次方,或許可以利用這點來進行篩選,將值為0以及值不為2的n次方的成員過濾
// 接續上面的GetFlags再做一次篩選
IEnumerable<T> GetUniqueFlags<T>(T theEnum)
{
foreach (var e in GetFlags(theEnum))
{
int i = Convert.ToInt32(e);
if (i != 0 && ((i & (i-1)) == 0))
yield return e;
}
}
這裡的 ((i & (i-1)) == 0) 就是在判斷 i 是否為2的n次方,使用了位元運算的一個小技巧
查看一下效果:
參考:
https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/builtin-types/enum
https://stackoverflow.com/questions/4171140/how-to-iterate-over-values-of-an-enum-having-flags
https://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2