Wie überprüfe ich, ob Flags einer Flaggenkombination gesetzt sind?

179

Nehmen wir an, ich habe diese Aufzählung:

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

Um zu überprüfen, ob zum Beispiel gesetzt ABist, kann ich dies tun:

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

Gibt es eine einfachere Möglichkeit zu überprüfen, ob eines der Flags einer kombinierten Flag-Konstante gesetzt ist als die folgenden?

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

Könnten Sie zum Beispiel das &mit etwas tauschen ?

Nicht zu stabil, wenn es um solche binären Dinge geht ...

Svish
quelle
Sollte nicht alles 'All = A | lauten B | C '?
Stevehipwell
4
AB | C entspricht A | B | C weil AB als A | definiert wurde B vorher.
Daniel Brückner
1
@ Daniel Brückner - Es ist gleichwertig, aber weniger lesbar. Besonders wenn die Aufzählung erweitert wurde.
Stevehipwell
Wahr. Ich kann es zum besseren Lesen ändern.
Svish

Antworten:

140

Wenn Sie wissen möchten, ob der Buchstabe einen der Buchstaben in AB enthält, müssen Sie den Operator AND & verwenden. Etwas wie:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}
yeyeyerman
quelle
2
Soweit ich sehen kann, macht dies den Job. Und hatte die klarsten Kommentare. Kompiliert jedoch nicht ohne Klammern letter & Letters.AB. Habe das da drin bearbeitet.
Svish
Auch wenn ich ein eingeführt habe Letters.None, nehme ich an, dass Sie das mit dem 0für einen weniger mit Magie vergleichbaren Look tauschen könnten ?
Svish
Natürlich. Aber ich denke nicht, dass der UND-Vergleich mit 0 streng genommen als magische Zahl angesehen werden kann.
Yeyeyerman
9
auch stackoverflow.com/questions/40211/how-to-compare-flags-in-c ist eine empfohlene Antwort, da es gegen das betreffende Element prüft, anstatt zu prüfen, ob es gleich 0 ist
dan richardson
@danrichardson Das Problem bei der Überprüfung des genauen Elements besteht darin, dass der Fall beseitigt wird, in dem ein Teil des zusammengesetzten Werts festgelegt wird (entweder A oder B), was vom OP nicht gewünscht wird.
Tom Lint
180

In .NET 4 können Sie die Enum.HasFlag-Methode verwenden :

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

Das Beispiel zeigt die folgende Ausgabe:

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.
Chuck Kasabula
quelle
13
Dies geht nicht auf die OP-Frage ein. Sie müssen noch && mehr hasFlag Operationen bestimmen , ob alle Flags gesetzt sind. Die Frage ist also, petsInFamilyhat entweder eine Pet.Dog || Pet.Cat?
GoClimbColorado
1
Siehe Mr. Skeets
GoClimbColorado
59

Ich benutze Erweiterungsmethoden, um solche Dinge zu schreiben:

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

Hier ist der Code:

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;
    }
}
Thomas Levesque
quelle
1
Sie könnten es so etwas enger machen : where T : struct, IConvertible. Ansonsten toller Code!
Hamish Grubijan
@ HamishGrubijan, guter Punkt ... und Enums implementieren auch IFormattable und IComparable. Alle numerischen Typen implementieren diese Schnittstellen jedoch auch, sodass es nicht ausreicht, sie auszuschließen
Thomas Levesque,
Vielen Dank für das Teilen, aber Sie müssen nicht immer nach Aufzählungen suchen. IsFlagSet(this Enum value, Enum flag)ist ausreichend.
DJJJ
34

In .NET 4 oder höher gibt es die HasFlag- Methode.

if(letter.HasFlag(Letters.AB))
{
}
Artru
quelle
25

Wenn Sie .NET 4 oder höher als die HasFlag () -Methode verwenden können

Beispiele

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

gleich wie

letter.HasFlag(Letters.AB)
Luka
quelle
Sind Sie sicher, bitwise ORdass es "beide müssen gesetzt sein" und keine?
Klammern
1
bitwise ORwürde die Werte kombinieren, also 1000 | 0010 wird 1010 oder beide setzen
Armando
13

Wenn es Sie wirklich nervt, können Sie eine Funktion wie diese schreiben:

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))
{
   // ...
}
Tamas Czinege
quelle
1
Die Zeile return (value & flag) == flag;wird nicht kompiliert: "Operator '&' kann nicht auf Operanden vom Typ 'T' und 'T' angewendet werden" .
Fredrik Mörk
1
Ehrfurcht: Bei der Frage ging es nicht um binäre Operationen, sondern um die Vereinfachung der Syntax von bitmaskenbezogenen Operationen in C #. Es gibt bereits viele ausgezeichnete Fragen und Antworten zum Thema Binäroperation zu Stackoverflow. Sie müssen nicht überall neu veröffentlicht werden.
Tamas Czinege
Ich sollte empfehlen, dass diejenigen, die mit binären Operationen nicht vertraut sind, sich vertraut machen, da das Gerüst, um es oben zu verbergen, die Dinge meiner Meinung nach viel weniger lesbar macht. Natürlich ist meine 'rohe' Lösung unten im Vergleich zur Punktzahl dieser Lösung derzeit nicht so gut, also stimmen die Leute über ihre Präferenzen ab und ich muss das respektieren ;-)
Will
10

Um zu überprüfen, ob zum Beispiel AB eingestellt ist, kann ich Folgendes tun:

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

Gibt es eine einfachere Möglichkeit zu überprüfen, ob eines der Flags einer kombinierten Flag-Konstante gesetzt ist als die folgenden?

Dies prüft, ob sowohl A als auch B gesetzt sind, und ignoriert, ob andere Flags gesetzt sind.

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

Dies überprüft, ob entweder A oder B gesetzt ist, und ignoriert, ob andere Flags gesetzt sind oder nicht.

Dies kann vereinfacht werden zu:

if(letter & Letters.AB)

Hier ist das C für binäre Operationen; Es sollte einfach sein, dies auf C # anzuwenden:

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

Übrigens ist die Benennung der Variablen im Beispiel der Frage der singuläre "Buchstabe", was bedeuten könnte, dass sie nur einen einzelnen Buchstaben darstellt; Der Beispielcode macht deutlich, dass es sich um eine Reihe möglicher Buchstaben handelt und dass mehrere Werte zulässig sind. Erwägen Sie daher, die Variable 'Buchstaben' umzubenennen.

Wille
quelle
Wäre das nicht anything_and_a, a_and_or_c_and_anything_elseund both_ac_and_anything_elseimmer wahr sein? oder fehlt mir hier etwas
Svish
In diesem Fall können Sie sehen, auf welche Flags initialisiert wurde. Sollten Flags jedoch kein A enthalten, wäre (Flags & A) 0, was falsch ist. both_ac_and_anything_else stellt sicher, dass sowohl A als auch C gesetzt sind, ignoriert jedoch alle anderen Flags, die ebenfalls gesetzt sind (z. B. ist es wahr, ob B gesetzt ist oder nicht).
Will
Hm, einige davon enden als Zahlen und sind in C # nicht boolesch. Wie würden Sie sie in Boolesche Werte konvertieren?
Svish
Es ist nicht implizit für Sie konvertiert? Null ist gleichbedeutend mit 'falsch' und alle anderen Werte sind 'wahr'.
Will
4

Wie wäre es mit

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

?

Jakob Christensen
quelle
Ja! Dadurch werden die A- und B-Werte gefiltert und ignoriert, wenn C enthalten ist. Wenn es also> 0 ist, ist es auch A oder B oder AB.
Ehrfurcht
3
Dies funktioniert nicht zu 100% mit vorzeichenbehafteten Werten. ! = 0 ist aus diesem Grund besser als> 0.
Stevehipwell
4

Ich habe eine einfache Erweiterungsmethode erstellt, bei der keine EnumTypen überprüft werden müssen:

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

Es funktioniert auch mit nullbaren Aufzählungen. Die Standardmethode HasFlagfunktioniert nicht, daher habe ich eine Erweiterung erstellt, um dies ebenfalls abzudecken.

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

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

Ein einfacher 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));
}

Genießen!

Henk van Boeijen
quelle
4

Hier gibt es viele Antworten, aber ich denke, der idiomatischste Weg, dies mit Flags zu tun, wäre Letters.AB.HasFlag (Buchstabe) oder (Letters.A | Letters.B) .HasFlag (Buchstabe), wenn Sie dies nicht tun habe bereits Letters.AB. letter.HasFlag (Letters.AB) funktioniert nur, wenn beide vorhanden sind.

Novaterata
quelle
3

Würde das für dich funktionieren?

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

Grüße,

Sebastiaan

Sebastiaan M.
quelle
1

Sie können diese Erweiterungsmethode für Aufzählungen für jede Art von Aufzählungen verwenden:

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;
}
masehhat
quelle
0
if((int)letter != 0) { }
Lee
quelle
Sie könnten den gleichen Fehler machen wie ich - er möchte überprüfen, ob A oder B eingestellt ist, aber C ignorieren.
Daniel Brückner
Sie brauchen die Besetzung nicht, wenn Sie die Aufzählung gegen 0 prüfen.
stevehipwell
Dies würde prüfen, ob einer von allen festgelegt wurde, nicht, ob eine kombinierte Aufzählung festgelegt wurde.
Svish
0

Sie können einfach überprüfen, ob der Wert nicht Null ist.

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

Ich würde es jedoch für eine bessere Lösung halten, einen neuen Aufzählungswert mit dem Wert Null einzuführen und diesen Aufzählungswert erneut zu vergleichen (wenn möglich, da Sie in der Lage sein müssen, die Aufzählung zu ändern).

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

if (letter != Letters.None) { }

AKTUALISIEREN

Die Frage falsch gelesen - den ersten Vorschlag korrigiert und den zweiten Vorschlag einfach ignoriert.

Daniel Brückner
quelle
Sie brauchen die Besetzung nicht, wenn Sie die Aufzählung gegen 0 prüfen.
stevehipwell
0

Es gibt zwei Ansätze, die ich sehen kann, um zu überprüfen, ob ein Bit gesetzt ist.

Ansatz A.

if (letter != 0)
{
}

Dies funktioniert so lange, wie es Ihnen nichts ausmacht, nach allen Bits zu suchen, auch nach nicht definierten!

Ansatz B.

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

Dies überprüft nur die definierten Bits, solange Letters.All alle möglichen Bits darstellt.

Verwenden Sie für bestimmte Bits (ein oder mehrere Sätze) Ansatz B, um Buchstaben zu ersetzen. Alle durch die Bits, auf die Sie prüfen möchten (siehe unten).

if ((letter & Letters.AB) != 0)
{
}
stevehipwell
quelle
Sie könnten den gleichen Fehler machen wie ich - er möchte überprüfen, ob A oder B gesetzt ist, aber C ignorieren.
Daniel Brückner
-1

Sorry, aber ich werde es in VB zeigen :)

   <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 Die Aufzählung enthält also 1 + 4

Wiroko
quelle