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.