C# Flags Enum迭代與篩選

  • 288
  • 0

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