Jak sprawdzić, czy są ustawione flagi kombinacji flag?


180

Powiedzmy, że mam to wyliczenie:

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

Aby sprawdzić, czy na przykład ABjest ustawione, mogę to zrobić:

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

Czy istnieje prostszy sposób sprawdzenia, czy któraś z flag połączonej stałej flagi jest ustawiona niż poniższe?

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

Czy mógłbyś na przykład zamienić &coś na coś?

Niezbyt stabilny, jeśli chodzi o takie binarne rzeczy ...


Nie powinni wszyscy czytać „All = A | B | DO'?
— stevehipwell

4
AB | C jest równoważne A | B | C, ponieważ AB zostało zdefiniowane jako A | B wcześniej.
— Daniel Brückner

1
@Daniel Brückner - Jest równoważny, ale jest mniej czytelny. Zwłaszcza jeśli wyliczenie zostało rozszerzone.
— stevehipwell

Prawdziwe. Mogę to zmienić dla lepszego czytania.
— Svish

Odpowiedzi:


145

Jeśli chcesz wiedzieć, czy litera ma którąkolwiek z liter w AB, musisz użyć operatora AND &. Coś jak:

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

2
O ile widzę, to spełnia swoje zadanie. I miał najwyraźniejsze komentarze. Nie kompiluje się jednak bez nawiasów wokół letter & Letters.AB. Edytowałem to tam.
— Svish

Również gdybym wprowadził a Letters.None, zakładam, że mógłbyś zamienić to 0na mniejszy w porównaniu z magiczną liczbą?
— Svish

Oczywiście. Ale nie sądzę, aby porównanie AND z 0 mogło być traktowane jako liczba magiczna ściśle.
— yeyeyerman

9
również stackoverflow.com/questions/40211/how-to-compare-flags-in-c jest zalecaną odpowiedzią, ponieważ sprawdza względem przedmiotowego elementu, a nie sprawdza, czy jest równy 0
— dan richardson

@danrichardson problem ze sprawdzaniem dokładnej pozycji polega na tym, że eliminuje to przypadek, gdy część wartości złożonej jest ustawiona (A lub B), co nie jest tym, czego chce OP.
— Tom Lint

181

W .NET 4 można skorzystać z metody 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);   
   }
}

Przykład wyświetla następujące dane wyjściowe:

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

14
To nie rozwiązuje kwestii PO. Nadal musisz && wiele operacji HasFlag, aby określić, czy są ustawione jakiekolwiek flagi. Więc pytanie brzmi, czy petsInFamilyma albo Pet.Dog || Pet.Cat?
— GoClimbColorado

1
Zobacz jasną odpowiedź pana Skeeta ... HasFlags Multiple
— GoClimbColorado

59

Używam metod rozszerzających, aby pisać takie rzeczy:

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

Oto kod:

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
Można uczynić go nieco mocniej tak: where T : struct, IConvertible. Świetny kod inaczej!
— Hamish Grubijan

@HamishGrubijan, dobra uwaga ... i wyliczenia również implementują IFormattable i IComparable. Jednak wszystkie typy liczbowe również implementują te interfejsy, więc nie wystarczy ich wykluczyć
— Thomas Levesque

Dzięki za udostępnienie, ale nie zawsze musisz sprawdzać wyliczenie. IsFlagSet(this Enum value, Enum flag)jest wystarczający.
— djmj


26

Jeśli możesz użyć .NET 4 lub nowszego, użyj metody HasFlag ()

przykłady

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

taki sam jak

letter.HasFlag(Letters.AB)

Czy na pewno bitwise ORoznacza to, że „oba muszą być ustawione”, a nie żadne?
— Brackets

1
bitwise ORpołączy wartości, więc 1000 | 0010 staje się 1010 lub oba ustawione
— Armando

13

Jeśli naprawdę cię to denerwuje, możesz napisać taką funkcję:

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
Linia return (value & flag) == flag;nie kompiluje się: „Operator '&' nie może być zastosowany do operandów typu 'T' i 'T'” .
— Fredrik Mörk

1
awe: Pytanie nie dotyczyło operacji binarnych, chodziło o uproszczenie składni operacji związanych z maską bitową w C #. Istnieje już wiele doskonałych pytań i odpowiedzi związanych z operacjami binarnymi na temat przepełnienia stosu, nie ma potrzeby ponownego publikowania ich wszędzie.
— Tamas Czinege

Powinienem polecić, aby osoby nieobeznane z operacjami binarnymi zaznajomiły się, ponieważ rusztowanie do ukrycia tego powyżej faktycznie sprawia, że ​​moim zdaniem jest znacznie mniej czytelny. Oczywiście moje `` surowe '' rozwiązanie poniżej nie radzi sobie obecnie tak dobrze w porównaniu z wynikiem tego rozwiązania, więc ludzie głosują na swoje preferencje i muszę to uszanować ;-)
— Will

10

Aby sprawdzić, czy na przykład ustawiono AB, mogę to zrobić:

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

Czy istnieje prostszy sposób sprawdzenia, czy któraś z flag połączonej stałej flagi jest ustawiona niż poniższe?

Sprawdza, czy ustawione są zarówno A, jak i B, i ignoruje, czy są ustawione inne flagi.

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

To sprawdza, czy jest zbiór A lub B i ignoruje, czy jakiekolwiek inne flagi są ustawione czy nie.

Można to uprościć, aby:

if(letter & Letters.AB)

Oto C dla operacji binarnych; powinno być proste zastosowanie tego do 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));

Nawiasem mówiąc, nazewnictwo zmiennej w przykładzie pytania to pojedyncza „litera”, co może sugerować, że reprezentuje ona tylko jedną literę; przykładowy kod jasno pokazuje, że jest to zestaw możliwych liter i dozwolonych jest wiele wartości, więc rozważ zmianę nazwy zmiennej „litery”.


Nie anything_and_a, a_and_or_c_and_anything_elsei both_ac_and_anything_elsezawsze było prawdziwe? czy coś mi tu brakuje?
— Svish

W takim przypadku możesz zobaczyć, do jakich flag zostały zainicjowane. Jednak jeśli flagi nie zawierają A, wtedy (flagi & A) będą równe 0, co jest fałszywe. both_ac_and_anything_else zapewnia, że ​​ustawione są zarówno A, jak i C, ale ignoruje wszelkie inne ustawione flagi (np. prawda, czy B jest ustawione, czy nie).
— Będzie

Hm, niektóre z nich kończą się jako liczby, a nie logiczne w C #. Jak przekonwertowałbyś je na wartości logiczne?
— Svish

Nie jest to dla ciebie domyślnie nawrócone? Zero jest równoważne „fałszowi”, a wszystkie inne wartości to „prawda”.
— Będzie

4

Co powiesz na

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

?


Tak! Spowoduje to odfiltrowanie wartości A i B i zignoruje, jeśli uwzględniono C. Więc jeśli jest> 0, jest również A lub B lub AB.
— awe

3
To nie działa w 100% z wartościami ze znakiem. ! = 0 jest lepsze niż> 0 z tego powodu.
— stevehipwell

4

Stworzyłem prostą metodę rozszerzenia, która nie wymaga sprawdzania Enumtypów:

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

Działa również na wyliczeniach dopuszczających wartość null. Standardowa HasFlagmetoda nie działa, więc stworzyłem rozszerzenie, które to obejmuje.

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

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

Prosty test:

[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));
}

Cieszyć się!


4

Jest tutaj wiele odpowiedzi, ale myślę, że najbardziej idiomatycznym sposobem na zrobienie tego z Flags byłoby Letters.AB.HasFlag (letter) lub (Letters.A | Letters.B) .HasFlag (letter), jeśli tego nie zrobiłeś już masz Letters.AB. letter.HasFlag (Letters.AB) działa tylko wtedy, gdy ma oba.


3

Czy to zadziała dla Ciebie?

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

Pozdrowienia,

Sebastiaan


1

Możesz użyć tej metody rozszerzenia na wyliczeniu dla dowolnego typu wyliczeń:

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) { }

Możesz popełnić ten sam błąd co ja - chce sprawdzić, czy A czy B jest ustawione, ale zignorować C.
— Daniel Brückner

Nie potrzebujesz obsady, jeśli sprawdzasz enum przeciwko 0.
— stevehipwell

Spowoduje to sprawdzenie, czy którykolwiek z nich został ustawiony, a nie, czy ustawiono którekolwiek z połączonych wyliczeń.
— Svish

0

Możesz po prostu sprawdzić, czy wartość nie wynosi zero.

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

Ale uważałbym, że lepszym rozwiązaniem byłoby wprowadzenie nowej wartości wyliczenia o wartości zero i porównanie z tą wartością wyliczenia (jeśli to możliwe, ponieważ musisz mieć możliwość modyfikowania wyliczenia).

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

if (letter != Letters.None) { }

AKTUALIZACJA

Nieprzeczytaj pytania - naprawiono pierwszą sugestię i po prostu zignoruj ​​drugą sugestię.


Nie potrzebujesz obsady, jeśli sprawdzasz wyliczenie z 0.
— stevehipwell

0

Widzę, że istnieją dwa sposoby sprawdzania, czy ustawiany jest dowolny bit.

Aproach A

if (letter != 0)
{
}

Działa to tak długo, jak nie masz nic przeciwko sprawdzaniu wszystkich bitów, w tym również niezdefiniowanych!

Aproach B

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

To sprawdza tylko zdefiniowane bity, o ile Letters.All reprezentuje wszystkie możliwe bity.

Dla określonych bitów (jeden lub więcej zestawów), użyj Aproach B zastępując litery Wszystkie z bitami, które chcesz sprawdzić (patrz poniżej).

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

Możesz popełnić ten sam błąd co ja - chce sprawdzić, czy A czy B jest ustawione, ale zignorować C.
— Daniel Brückner

-1

Przepraszam, ale pokażę to w 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 Więc wyliczenie zawiera 1 + 4

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.