フラグを持つEnumの値を反復する方法は?


131

フラグの列挙型を保持する変数がある場合、その特定の変数のビット値をどうにかして反復できますか?または、Enum.GetValuesを使用して列挙型全体を反復処理し、どれが設定されているかを確認する必要がありますか?


APIを制御できる場合は、ビットフラグの使用を避けます。それらが有用な最適化になることはめったにありません。いくつかのパブリック 'bool'フィールドで構造体を使用することは、意味的には同等ですが、コードは劇的に単純です。また、後で必要になった場合は、フィールドを内部でビットフィールドを操作するプロパティに変更して、最適化をカプセル化できます。
ジェイバズジ

2
私はあなたの言っていることを理解しており、多くの場合それは理にかなっていますが、この場合、If提案と同じ問題が発生します...代わりに12の異なるブール値に対してIfステートメントを記述する必要があります配列に対して単純なforeachループを使用する方法。(そして、これはパブリックDLLの一部であるため、ブールの配列や何があるかなどの難解なことはできません。)

10
「コードは劇的に単純になります」-個々のビットをテストする以外のことを行うと、まったく逆になります。フィールドとプロパティはファーストクラスのエンティティではないため、ループと集合演算は事実上不可能になります。
ジムバルター2013年

2
@nawfal「少し」私はあなたがそこで何をしたかを見ます
stannius

回答:


179
static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}

7
HasFlag.NET 4以降で使用できることに注意してください。
Andreas Grech 2013年

3
これは素晴らしい!しかし、あなたはそれをさらにシンプルで使いやすくすることができます。これを拡張メソッドとして貼り付けるEnum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag); だけです。 次に:myEnum.GetFLags():)
joshcomley 2013

3
すばらしいワンライナー、ジョシュですが、それでも、上記のジェフの回答のように、単一フラグ値(Bar、Baz)だけではなく、複数フラグ値(Boo)を取得するという問題があります。

10
ニース-ただし、Noneには注意してください-たとえば、Items.Noneは常に含まれます
Ilan

1
メソッドの署名はstatic IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers、2015年

48

これは、問題のLinqソリューションです。

public static IEnumerable<Enum> GetFlags(this Enum e)
{
      return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}

7
なぜこれが一番上にないのですか?:) .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));表す値がゼロの場合に使用しますNone
georgiosd

超きれい。私の意見では最良の解決策です。
ライアンフィオリーニ

@georgiosdパフォーマンスはそれほど良くないと思います。(しかし、ほとんどのタスクには十分なはずです)
AntiHeadshot

41

私の知る限り、各コンポーネントを取得するための組み込みメソッドはありません。しかし、ここにそれらを取得する1つの方法があります。

[Flags]
enum Items
{
    None = 0x0,
    Foo  = 0x1,
    Bar  = 0x2,
    Baz  = 0x4,
    Boo  = 0x6,
}

var value = Items.Foo | Items.Bar;
var values = value.ToString()
                  .Split(new[] { ", " }, StringSplitOptions.None)
                  .Select(v => (Items)Enum.Parse(typeof(Items), v));

// This method will always end up with the most applicable values
value = Items.Bar | Items.Baz;
values = value.ToString()
              .Split(new[] { ", " }, StringSplitOptions.None)
              .Select(v => (Items)Enum.Parse(typeof(Items), v)); // Boo

Enum代わりにフラグを返す文字列を生成するために内部で何を行うかを調整しました。あなたは反射板でコードを見ることができ、多かれ少なかれ同等であるはずです。複数のビットを含む値がある一般的な使用例に適しています。

static class EnumExtensions
{
    public static IEnumerable<Enum> GetFlags(this Enum value)
    {
        return GetFlags(value, Enum.GetValues(value.GetType()).Cast<Enum>().ToArray());
    }

    public static IEnumerable<Enum> GetIndividualFlags(this Enum value)
    {
        return GetFlags(value, GetFlagValues(value.GetType()).ToArray());
    }

    private static IEnumerable<Enum> GetFlags(Enum value, Enum[] values)
    {
        ulong bits = Convert.ToUInt64(value);
        List<Enum> results = new List<Enum>();
        for (int i = values.Length - 1; i >= 0; i--)
        {
            ulong mask = Convert.ToUInt64(values[i]);
            if (i == 0 && mask == 0L)
                break;
            if ((bits & mask) == mask)
            {
                results.Add(values[i]);
                bits -= mask;
            }
        }
        if (bits != 0L)
            return Enumerable.Empty<Enum>();
        if (Convert.ToUInt64(value) != 0L)
            return results.Reverse<Enum>();
        if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L)
            return values.Take(1);
        return Enumerable.Empty<Enum>();
    }

    private static IEnumerable<Enum> GetFlagValues(Type enumType)
    {
        ulong flag = 0x1;
        foreach (var value in Enum.GetValues(enumType).Cast<Enum>())
        {
            ulong bits = Convert.ToUInt64(value);
            if (bits == 0L)
                //yield return value;
                continue; // skip the zero value
            while (flag < bits) flag <<= 1;
            if (flag == bits)
                yield return value;
        }
    }
}

拡張メソッドGetIndividualFlags()は、型の個々のフラグをすべて取得します。したがって、複数のビットを含む値は省略されます。

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz

文字列分割を行うことを検討しましたが、それはおそらく、列挙型のビット値全体を反復するよりもはるかに多くのオーバーヘッドになります。

残念ながら、そうすることで、冗長な値をテストする必要があります(必要ない場合)。それがもたらすであろう、私の第二の例を参照してくださいBarBazBooだけではなくBoo
ジェフメルカード

興味深いことに、Booをそこから取り除くことができますが、その部分は私がやっていることには不要です(実際、非常に悪い考えです:))。

これは興味深いように見えますが、私が誤解していない場合は、上記の列挙型に対してBooが返されます。ここでは、他の値の組み合わせではないバージョン(つまり、2のべき乗であるバージョン)のみを列挙します。 。それは簡単にできますか?私はこれまで努力してきましたが、FP演算に頼らずにそれを特定する簡単な方法は考えられません。

@ロビン:そうです、元Booの値が返されます(値はを使用して返されますToString())。個別のフラグのみを許可するように調整しました。したがって、私の例ではBar、のBaz代わりにを取得できますBoo
ジェフメルカド2010年

26

数年後にこれに戻って、もう少し経験を積んで、最低ビットから最高ビットに移動する単一ビット値のみに対する私の最終的な答えは、ジェフメルカドの内部ルーチンのわずかな変形です:

public static IEnumerable<Enum> GetUniqueFlags(this Enum flags)
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<Enum>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value))
        {
            yield return value;
        }
    }
}

それは機能しているようで、数年前の私の反対にもかかわらず、ここではHasFlagを使用しています。これは、ビットごとの比較を使用するよりもはるかに読みやすく、速度の違いが私がやろうとしていることにとって重要ではないためです。(それ以来、とにかくHasFlagsの速度が改善された可能性は十分にあります。私が知っているすべてのことについて...私はテストしていません。)


ノートフラグをintに初期化するだけで、ビットなどのulongに初期化し、1ul
forcewill

ありがとう、修正します!(実際に行って実際のコードを確認したところ、具体的にはulongであると宣言することにより、すでに逆の方法で修正していました。)

2
これは私が見つけた唯一の解決策であり、「ゼロ」を表すはずの値ゼロのフラグがある場合、他の回答GetFlag()メソッドがフラグの1つとしてYourEnum.Noneを返すという事実にも影響を受けないようですそれが実際に列挙型にない場合でも、メソッドを実行しています!ゼロ以外の列挙フラグが1つしか設定されていない場合、メソッドが予想以上に実行されるため、奇妙な重複ログエントリが発生していました。この素晴らしいソリューションを更新して追加していただきありがとうございます。
BrianH

yield return bits;
Jaider

1
私はulong.MaxValueを割り当てた「All」列挙型変数が必要だったので、すべてのビットが「1」に設定されています。しかし、フラグ<ビットは真に評価されないため、コードは無限ループにぶつかります(フラグは負にループしてから0でスタックします)。
Haighstrom 2017

15

@Gregの方法から抜け出しますが、C#7.3からの新しい機能を追加すると、Enum制約は次のようになります。

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
    where T : Enum    // New constraint for C# 7.3
{
    foreach (Enum value in Enum.GetValues(flags.GetType()))
        if (flags.HasFlag(value))
            yield return (T)value;
}

新しい制約により、これをを介してキャストすることなく、拡張メソッドにすること(int)(object)eができます。このHasFlagメソッドを使用して、Tからに直接キャストできますvalue

C#7.3では、デリゲートとの制約も追加されましたunmanaged


4
おそらく、flagsパラメーターTもジェネリック型にする必要がありました。そうでない場合は、呼び出すたびに列挙型を明示的に指定する必要があります。
Ray

11

@ RobinHood70が提供する回答の+1。メソッドのジェネリックバージョンが便利だとわかりました。

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("The generic type parameter must be an Enum.");

    if (flags.GetType() != typeof(T))
        throw new ArgumentException("The generic type parameter does not match the target type.");

    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}

EDIT および@AustinWBryanの+1は、C#7.3をソリューションスペースにもたらします。

public static IEnumerable<T> GetUniqueFlags<T>(this T flags) where T : Enum
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}

3

すべての値を繰り返す必要はありません。次のように特定のフラグを確認してください:

if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1) 
{
   //do something...
}

または(pstrjdsがコメントで言ったように)次のように使用するかどうかを確認できます。

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}

5
.Net 4.0を使用している場合、同じことを行うために使用できる拡張メソッドHasFlagがあります。myVar.HasFlag(FlagsEnum.Flag1)
pstrjds

1
プログラマーがビット単位のAND演算を理解できない場合は、それをまとめて新しいキャリアを見つける必要があります。
Ed S.

2
@Ed:真ですが、もう一度コードを読んでいるときはHasFlagの方が優れています...(おそらく数か月または数年後に)
Dr TJ

4
@Ed Swangren:ビットワイズ演算の使用が "難しい"とは限らないため、コードの可読性と冗長性を低くすることが重要です。
ジェフメルカド2010年

2
HasFlagは非常に遅いです。HasFlagとビットマスキングを使用して大きなループを試すと、大きな違いに気付くでしょう。

3

メソッドの入力パラメーターをenum型として入力するのではなく、アプローチを変更しました。enum型(MyEnum[] myEnums)の配列として入力しました。このようにして、ループ内のswitchステートメントで配列を反復処理します。


2

上記の答えには満足していませんでした。

ここでいくつかの異なるソースをつなぎ合わせた後:
このスレッドのSO QnA
コードプロジェクトの前のポスターEnum Flags Check Post
Great Enum <T>ユーティリティ

これを作成したので、あなたの考えを教えてください。
パラメータ::フラグ値として
bool checkZero許可するように指示し0ます。デフォルトでinput = 0は空を返します。
bool checkFlags:属性でEnum装飾されているかどうかを確認するように指示し[Flags]ます。
PS。現時点checkCombinators = falseでは、ビットの組み合わせである列挙値を無視するように強制するalg を理解する時間はありません。

    public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum input, bool checkZero = false, bool checkFlags = true, bool checkCombinators = true)
    {
        Type enumType = typeof(TEnum);
        if (!enumType.IsEnum)
            yield break;

        ulong setBits = Convert.ToUInt64(input);
        // if no flags are set, return empty
        if (!checkZero && (0 == setBits))
            yield break;

        // if it's not a flag enum, return empty
        if (checkFlags && !input.GetType().IsDefined(typeof(FlagsAttribute), false))
            yield break;

        if (checkCombinators)
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum<TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }
        else
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum <TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }

    }

これは、ここで使用するyield returnために更新した、ここにあるヘルパークラスEnum <T>を利用しGetValuesます。

public static class Enum<TEnum>
{
    public static TEnum Parse(string value)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    public static IEnumerable<TEnum> GetValues()   
    {
        foreach (object value in Enum.GetValues(typeof(TEnum)))
            yield return ((TEnum)value);
    }
}  

最後に、これを使用する例を示します。

    private List<CountType> GetCountTypes(CountType countTypes)
    {
        List<CountType> cts = new List<CountType>();

        foreach (var ct in countTypes.GetFlags())
            cts.Add(ct);

        return cts;
    }

申し訳ありませんが、このプロジェクトを数日見る時間はありませんでした。コードをよく確認できたら、折り返しご連絡いたします。

4
そのコードにバグがあることを示しています。if(checkCombinators)ステートメントの両方のブランチのコードは同じです。また、おそらくバグではないが、予期しないことですが、enum値が0と宣言されている場合、常にコレクションに返されます。checkZeroがtrueで他にフラグが設定されていない場合にのみ返されるようです。
dhochee

@dhochee。同意する。または、コードはかなりいいですが、引数は紛らわしいです。
AFract

2

上記のGregの答えに基づいて、これは、None = 0など、列挙型に値0がある場合にも対応します。その場合、その値を反復するべきではありません。

public static IEnumerable<Enum> ToEnumerable(this Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value) && Convert.ToInt64(value) != 0)
            yield return value;
}

これをさらに改善して、列挙型のすべてのフラグが、すべての基になる列挙型を処理できる非常にスマートな方法で設定される場合と、All =〜0およびAll = EnumValue1 |の場合を処理できるようにする方法を知っていますか?EnumValue2 | EnumValue3 | ...


1

Enumのイテレータを使用できます。MSDNコードから始める:

public class DaysOfTheWeek : System.Collections.IEnumerable
{
    int[] dayflag = { 1, 2, 4, 8, 16, 32, 64 };
    string[] days = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
    public string value { get; set; }

    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < days.Length; i++)
        {
            if value >> i & 1 == dayflag[i] {
                yield return days[i];
            }
        }
    }
}

テストされていないので、間違えた場合は遠慮なく電話してください。(明らかにそれは再入可能ではありません。)事前に値を割り当てるか、enum.dayflagとenum.daysを使用する別の関数に値を分割する必要があります。あなたはアウトラインでどこかに行くことができるかもしれません。


0

次のコードも同様です。

public static string GetEnumString(MyEnum inEnumValue)
{
    StringBuilder sb = new StringBuilder();

    foreach (MyEnum e in Enum.GetValues(typeof(MyEnum )))
    {
        if ((e & inEnumValue) != 0)
        {
           sb.Append(e.ToString());
           sb.Append(", ");
        }
    }

   return sb.ToString().Trim().TrimEnd(',');
}

enum値が値に含まれている場合にのみ、内部に入ります


0

すべての回答は単純なフラグでうまく機能します。フラグを組み合わせると、おそらく問題が発生します。

[Flags]
enum Food
{
  None=0
  Bread=1,
  Pasta=2,
  Apples=4,
  Banana=8,
  WithGluten=Bread|Pasta,
  Fruits = Apples | Banana,
}

おそらく、列挙値自体が組み合わせであるかどうかをテストするためのチェックを追加する必要があります。 要件をカバーするには、おそらくHenk van Boeijenがここに投稿したようなものが必要です(少し下にスクロールする必要があります)。


0

新しいEnum制約とジェネリックを使用してキャストを防止する拡張メソッド:

public static class EnumExtensions
{
    public static T[] GetFlags<T>(this T flagsEnumValue) where T : Enum
    {
        return Enum
            .GetValues(typeof(T))
            .Cast<T>()
            .Where(e => flagsEnumValue.HasFlag(e))
            .ToArray();
    }
}

-1

intに変換することで直接行うことができますが、型チェックが失われます。最善の方法は私の提案に似たものを使うことだと思います。それはずっと適切なタイプを保ちます。変換は必要ありません。パフォーマンスに少しの影響を与えるボクシングのため、それは完璧ではありません。

完璧ではありませんが(ボクシング)、警告なしで機能します...

/// <summary>
/// Return an enumerators of input flag(s)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static IEnumerable<T> GetFlags<T>(this T input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
    {
        if ((int) (object) value != 0) // Just in case somebody has defined an enum with 0.
        {
            if (((Enum) (object) input).HasFlag(value))
                yield return (T) (object) value;
        }
    }
}

使用法:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.