フラグの組み合わせのフラグが設定されているかどうかを確認するにはどうすればよいですか?


180

私がこの列挙型を持っているとしましょう:

[Flags]
enum Letters
{
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = A | B | C,
}

たとえばAB設定されているかどうかを確認するには、次のようにします。

if((letter & Letters.AB) == Letters.AB)

結合されたフラグ定数のフラグのいずれかが以下よりも設定されているかどうかを確認する簡単な方法はありますか?

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

たとえば&、何かを交換できますか?

このようなバイナリのものに関してはあまり安定していません...


すべてを読むべきではありません 'All = A | B | C '?
stevehipwell 2009

4
AB | CはAと同等| B | ABがAとして定義されたため、C | 前B。
DanielBrückner

1
@DanielBrückner-同等ですが、読みにくくなります。特に、列挙型が拡張された場合。
stevehipwell 2009

そうだね。読みやすくするために変更できます。
Svish

回答:


145

文字にABの文字が含まれているかどうかを知りたい場合は、AND&演算子を使用する必要があります。何かのようなもの:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}

2
私の知る限り、これで十分です。そして、最も明確なコメントがありました。かっこがないとコンパイルできませんletter & Letters.AB。そこで編集しました。
Svish

また、を導入した場合Letters.None、それをと0比較して、マジックナンバーとの比較を少なくすることができると思いますか?
Svish

もちろん。しかし、私は0とのAND比較が厳密にマジックナンバーと考えられるとは思いません。
yeyeyerman 2009

9
またstackoverflow.com/questions/40211/how-to-compare-flags-in-cそれが0に等しいかどうかのチェックとは対照的に、問題の項目に対して、それをチェックとして推奨答えである
ダン・リチャードソン

@danrichardson正確なアイテムのチェックの問題は、複合値の一部(AまたはBのいずれか)が設定されている場合に、OPが望まないケースを排除することです。
Tom Lint、

181

.NET 4では、Enum.HasFlagメソッドを使用できます。

using System;

[Flags] public enum Pet {
   None = 0,
   Dog = 1,
   Cat = 2,
   Bird = 4,
   Rabbit = 8,
   Other = 16
}

public class Example
{
   public static void Main()
   {
      // Define three families: one without pets, one with dog + cat and one with a dog only
      Pet[] petsInFamilies = { Pet.None, Pet.Dog | Pet.Cat, Pet.Dog };
      int familiesWithoutPets = 0;
      int familiesWithDog = 0;

      foreach (Pet petsInFamily in petsInFamilies)
      {
         // Count families that have no pets. 
         if (petsInFamily.Equals(Pet.None))
            familiesWithoutPets++;
         // Of families with pets, count families that have a dog. 
         else if (petsInFamily.HasFlag(Pet.Dog))
            familiesWithDog++;
      }
      Console.WriteLine("{0} of {1} families in the sample have no pets.", 
                        familiesWithoutPets, petsInFamilies.Length);   
      Console.WriteLine("{0} of {1} families in the sample have a dog.", 
                        familiesWithDog, petsInFamilies.Length);   
   }
}

この例では、次の出力が表示されます。

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.

14
これはOPの質問には対応していません。フラグが設定されているかどうを判断するには、複数のHasFlag操作を&&する必要があります。だから問題はpetsInFamilyどちらかを持っていますかPet.Dog || Pet.Cat
GoClimbColorado 2015

1
スキート氏の明確な答えを見てください... HasFlags Multiple
GoClimbColorado

59

私はそのようなものを書くために拡張メソッドを使用します:

if (letter.IsFlagSet(Letter.AB))
    ...

これがコードです:

public static class EnumExtensions
{
    private static void CheckIsEnum<T>(bool withFlags)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(true);
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static string GetDescription<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(false);
        string name = Enum.GetName(typeof(T), value);
        if (name != null)
        {
            FieldInfo field = typeof(T).GetField(name);
            if (field != null)
            {
                DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attr != null)
                {
                    return attr.Description;
                }
            }
        }
        return null;
    }
}

1
次のように少しきつくすることができますwhere T : struct, IConvertible。そうでなければ素晴らしいコード!
Hamish Grubijan 2012

@HamishGrubijan、良い点...そして列挙型はIFormattableとIComparableも実装します。それは、それらを除外するために十分ではありませんので、しかし、すべての数値型は、あまりにもこれらのインタフェースを実装する
トーマス・レベスク

共有していただきありがとうございます。必ずしも列挙型を確認する必要はありません。IsFlagSet(this Enum value, Enum flag)十分なものです。
djmj 14


26

HasFlag()メソッドを使用するよりも.NET 4以上を使用できる場合

letter.HasFlag(Letters.A | Letters.B) // both A and B must be set

と同じ

letter.HasFlag(Letters.AB)

bitwise OR「どちらも設定する必要があります」ではなく、本当に設定しますか?
ブラケット

1
bitwise OR値を組み合わせるため、1000 | 0010は1010になるか、両方が設定されます
Armando

13

それが本当にあなたを困らせるなら、あなたはそのような関数を書くことができます:

public bool IsSet(Letters value, Letters flag)
{
    return (value & flag) == flag;
}

if (IsSet(letter, Letters.A))
{
   // ...
}

// If you want to check if BOTH Letters.A and Letters.B are set:
if (IsSet(letter, Letters.A & Letters.B))
{
   // ...
}

// If you want an OR, I'm afraid you will have to be more verbose:
if (IsSet(letter, Letters.A) || IsSet(letter, Letters.B))
{
   // ...
}

1
この行return (value & flag) == flag;はコンパイルされません:「演算子 '&'は、タイプ 'T'および 'T'のオペランドには適用できません
FredrikMörk、

1
畏敬の念:質問はバイナリ演算に関するものではなく、C#でのビットマスク関連演算の構文の簡略化に関するものでした。stackoverflowには、すでに優れたバイナリ演算関連の質問と回答がたくさんあります。どこにでも再投稿する必要はありません。
Tamas Czinege 2009

バイナリ演算に不慣れな方は、慣れることをお勧めします。上にそれを隠すためのスキャフォールディングによって、実際には物事が読みにくくなります。人々は自分の好みに投票していると私は;-)それを尊重しなければならないので、もちろん私の下の「生の」ソリューションは現在、このソリューションのスコアに比べてとてもうまくやっていません
ウィル

10

たとえばABが設定されているかどうかを確認するには、次のようにします。

if((letter&Letters.AB)== Letters.AB)

結合されたフラグ定数のフラグのいずれかが以下よりも設定されているかどうかを確認する簡単な方法はありますか?

これは、AとBの両方が設定されていることを確認し他のフラグが設定されているかどうかを無視します。

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

これは、AまたはBが設定されていることを確認し他のフラグが設定されているかどうかを無視します。

これは次のように簡略化できます。

if(letter & Letters.AB)

二項演算のCは次のとおりです。これをC#に適用するのは簡単です。

enum {
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = AB | C,
};

int flags = A|C;

bool anything_and_a = flags & A;

bool only_a = (flags == A);

bool a_and_or_c_and_anything_else = flags & (A|C);

bool both_ac_and_anything_else = (flags & (A|C)) == (A|C);

bool only_a_and_c = (flags == (A|C));

ちなみに、質問の例での変数の命名は単一の「文字」であり、これは単一の文字のみを表すことを意味する場合があります。サンプルコードでは、可能な文字のセットと複数の値が許可されていることが明確になっているため、変数「letters」の名前を変更することを検討してください。


ではないだろうanything_and_aa_and_or_c_and_anything_elseboth_ac_and_anything_else常に真でありますか?または私はここで何か不足していますか?
Svish

この場合、初期化されているフラグを確認できます。ただし、フラグにAが含まれていない場合、(flags&A)は0になり、falseになります。both_ac_and_anything_elseは、AとCの両方が設定されていることを確認しますが、設定されている他のフラグも無視します(たとえば、Bが設定されているかどうかにかかわらず、true)。
ウィル

うーん、それらのいくつかはC#ではブール値ではなく、数値として終わります。どのようにブール値に変換しますか?
Svish

暗黙的に変換されていませんか?ゼロは「false」と同等で、他のすべての値は「true」です。
ウィル

4

いかがですか

if ((letter & Letters.AB) > 0)


はい!これは、AとBの値でフィルタリングし、Cが含まれている場合は無視します。したがって、0より大きい場合は、AまたはBまたはABでもあります。
畏敬の念を示す、

3
これは、符号付きの値では100%機能しません。!= 0は、この理由から> 0よりも優れています。
stevehipwell 2009

4

Enumタイプのチェックを必要としない簡単な拡張メソッドを作成しました:

public static bool HasAnyFlag(this Enum value, Enum flags)
{
    return
        value != null && ((Convert.ToInt32(value) & Convert.ToInt32(flags)) != 0);
}

また、null許容列挙型でも機能します。標準のHasFlag方法ではサポートされていないため、それをカバーする拡張機能も作成しました。

public static bool HasFlag(this Enum value, Enum flags)
{
    int f = Convert.ToInt32(flags);

    return
        value != null && ((Convert.ToInt32(value) & f) == f);
}

簡単なテスト:

[Flags]
enum Option
{
    None = 0x00,
    One = 0x01,
    Two = 0x02,
    Three = One | Two,
    Four = 0x04
}

[TestMethod]
public void HasAnyFlag()
{
    Option o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

[TestMethod]
public void HasAnyFlag_NullableEnum()
{
    Option? o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

楽しい!


4

ここにはたくさんの答えがありますが、フラグを使用してこれを行う最も慣用的な方法は、Letters.AB.HasFlag(letter)または(Letters.A | Letters.B).HasFlag(letter)であると思いますすでにLetters.ABがあります。letter.HasFlag(Letters.AB)は、両方ある場合にのみ機能します。



1

この拡張メソッドは、任意のタイプの列挙型の列挙型で使用できます。

public static bool IsSingle(this Enum value)
{
    var items = Enum.GetValues(value.GetType());
    var counter = 0;
    foreach (var item in items)
    {
        if (value.HasFlag((Enum)item))
        {
            counter++;
        }
        if (counter > 1)
        {
            return false;
        }
    }
    return true;
}

0
if((int)letter != 0) { }

あなた、私がやったのと同じ過ちを可能性があります-彼はAまたはBがセットされているかどうかを確認しかしC.を無視したい
ダニエル・ブルックナー

enumを0に対してチェックしている場合は、キャストは必要ありません
。– stevehipwell

これにより、それらのすべてが設定されているかどうかが確認されます。結合された列挙型のいずれかが設定されているかどうかは確認されません。
Svish

0

値がゼロでないかどうかを確認できます。

if ((Int32)(letter & Letters.AB) != 0) { }

ただし、値0の新しい列挙値を導入し、この列挙値を再度比較することをお勧めします(列挙を変更できる必要があるため可能であれば)。

[Flags]
enum Letters
{
    None = 0,
    A    = 1,
    B    = 2,
    C    = 4,
    AB   =  A | B,
    All  = AB | C
}

if (letter != Letters.None) { }

更新

質問を間違えました-最初の提案を修正し、2番目の提案を無視します。


enumを0に対してチェックする場合は、キャストは必要ありません
。– stevehipwell

0

設定されているビットをチェックするために機能する2つのアプローチがあります。

アプローチA

if (letter != 0)
{
}

これは、定義されていないものも含め、すべてのビットをチェックしても構わない限り機能します。

アプローチB

if ((letter & Letters.All) != 0)
{
}

Letters.Allがすべての可能なビットを表す限り、これは定義されたビットのみをチェックします。

特定のビット(1つ以上のセット)については、Aletach Bを使用して、Letters.Allをチェックするビットに置き換えます(以下を参照)。

if ((letter & Letters.AB) != 0)
{
}

あなた、私がやったのと同じ過ちを可能性があります-彼はAまたはBがセットされているかどうかを確認しかしC.を無視したい
ダニエル・ブルックナー

-1

すみませんが、VBで表示します:)

   <Flags()> Public Enum Cnt As Integer
        None = 0
        One = 1
        Two = 2
        Three = 4
        Four = 8    
    End Enum

    Sub Test()
    Dim CntValue As New Cnt
    CntValue += Cnt.One
    CntValue += Cnt.Three
    Console.WriteLine(CntValue)
    End Sub

CntValue = 5したがって、列挙には1 + 4が含まれます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.