Wie iteriere ich über Werte einer Aufzählung mit Flags?

131

Wenn ich eine Variable habe, die eine Flags-Aufzählung enthält, kann ich dann irgendwie über die Bitwerte in dieser bestimmten Variablen iterieren? Oder muss ich Enum.GetValues ​​verwenden, um die gesamte Enumeration zu durchlaufen und zu überprüfen, welche festgelegt sind?

Olivier Rogier
quelle
Wenn Sie die Kontrolle über Ihre API haben, vermeiden Sie die Verwendung von Bit-Flags. Sie sind selten eine nützliche Optimierung. Die Verwendung einer Struktur mit mehreren öffentlichen 'bool'-Feldern ist semantisch äquivalent, aber Ihr Code ist erheblich einfacher. Und wenn Sie es später brauchen, können Sie die Felder in Eigenschaften ändern, die Bitfelder intern bearbeiten und die Optimierung kapseln.
Jay Bazuzi
2
Ich verstehe, was Sie sagen, und in vielen Fällen wäre es sinnvoll, aber in diesem Fall hätte ich das gleiche Problem wie der If-Vorschlag ... Ich müsste stattdessen If-Anweisungen für ein Dutzend verschiedener Bools schreiben der Verwendung einer einfachen foreach-Schleife über ein Array. (Und da dies Teil einer öffentlichen DLL ist, kann ich keine arkanen Dinge tun, wie nur eine Reihe von Bools zu haben oder was Sie haben.)
10
"Ihr Code ist dramatisch einfacher" - genau das Gegenteil ist der Fall, wenn Sie etwas anderes tun als nur einzelne Bits zu testen ... Schleifen und Set-Operationen werden praktisch unmöglich, da Felder und Eigenschaften keine erstklassigen Entitäten sind.
Jim Balter
2
@nawfal "ein bisschen" Ich sehe, was Sie dort getan haben
Stannius

Antworten:

178
static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}
Greg
quelle
7
Beachten Sie, dass dies HasFlagab .NET 4 verfügbar ist.
Andreas Grech
3
Das ist toll! Sie können es jedoch noch einfacher und benutzerfreundlicher gestalten. Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);myEnum.GetFLags()
Kleben Sie
3
Großartiger Einzeiler, Josh, aber es besteht immer noch das Problem, Multi-Flag-Werte (Boo) anstelle von Single-Flag-Werten (Bar, Baz) zu erfassen, wie in Jeffs Antwort oben.
10
Schön - achten Sie jedoch auf keine - z. B. Gegenstände. Keiner von Jeffs Antworten wird immer enthalten sein
Ilan
1
Methode Unterschrift sollte seinstatic IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers
48

Hier ist eine Linq-Lösung für das Problem.

public static IEnumerable<Enum> GetFlags(this Enum e)
{
      return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}
Agritton
quelle
7
Warum ist das nicht oben? :) Verwenden .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));Sie, wenn Sie einen Nullwert zur Darstellung habenNone
georgiosd
Super sauber. Beste Lösung meiner Meinung nach.
Ryan Fiorini
@georgiosd Ich denke, die Leistung ist nicht so toll. (
Sollte aber
41

Soweit ich weiß, gibt es keine integrierten Methoden, um jede Komponente abzurufen. Aber hier ist eine Möglichkeit, wie Sie sie erhalten können:

[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

Ich habe die Enuminternen Funktionen angepasst , um die Zeichenfolge zu generieren und stattdessen die Flags zurückzugeben. Sie können den Code im Reflektor betrachten und sollten mehr oder weniger gleichwertig sein. Funktioniert gut für allgemeine Anwendungsfälle, in denen Werte mehrere Bits enthalten.

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

Die Erweiterungsmethode GetIndividualFlags()ruft alle einzelnen Flags für einen Typ ab. Werte, die mehrere Bits enthalten, werden also weggelassen.

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz
Jeff Mercado
quelle
Ich hatte überlegt, einen String-Split durchzuführen, aber das ist wahrscheinlich viel mehr Aufwand als nur das Iterieren der Bitwerte der gesamten Enumeration.
Leider müssten Sie auf diese Weise auf redundante Werte testen (wenn Sie diese nicht möchten). Siehe mein zweites Beispiel, es würde nachgeben Bar, Bazund Boonicht nur Boo.
Jeff Mercado
Interessant, dass Sie Boo daraus herausholen können, obwohl dieser Teil für das, was ich tue, unnötig (und in der Tat eine wirklich schlechte Idee :)) ist.
Das sieht interessant aus, aber wenn ich nicht falsch verstehe, würde es Boo für die obige Aufzählung zurückgeben, wo ich nur die Versionen aufzählen möchte, die keine Kombinationen anderer Werte sind (dh diejenigen, die Zweierpotenzen sind). . Könnte das leicht gemacht werden? Ich habe es versucht und kann mir keinen einfachen Weg vorstellen, dies zu identifizieren, ohne auf FP-Mathematik zurückzugreifen.
@Robin: Sie haben Recht, das Original würde zurückkehren Boo(der Wert, der mit zurückgegeben wird ToString()). Ich habe es so angepasst, dass nur einzelne Flags zugelassen werden. Also in meinem Beispiel können Sie bekommen Barund Bazstatt Boo.
Jeff Mercado
26

Als ich einige Jahre später mit etwas mehr Erfahrung darauf zurückkomme, ist meine ultimative Antwort nur für Einzelbitwerte, die vom niedrigsten zum höchsten Bit übergehen, eine leichte Variante von Jeff Mercados innerer Routine:

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

Es scheint zu funktionieren, und trotz meiner Einwände vor einigen Jahren verwende ich hier HasFlag, da es weitaus besser lesbar ist als bitweise Vergleiche und der Geschwindigkeitsunterschied für alles, was ich tun werde, unbedeutend ist. (Es ist durchaus möglich, dass sie seitdem die Geschwindigkeit von HasFlags verbessert haben, soweit ich weiß ... ich habe es nicht getestet.)


quelle
Nur ein Notenflag wird zu einem int initialisiert, sollte zu einem ulong wie Bits initialisiert werden, sollte als 1ul
forcewill
Danke, das werde ich beheben! (Ich habe gerade meinen tatsächlichen Code überprüft und ihn bereits umgekehrt behoben, indem ich ihn ausdrücklich als ulong deklariert habe.)
2
Dies ist die einzige Lösung, die ich gefunden habe und die anscheinend auch nicht unter der Tatsache leidet, dass andere GetFlag () -Methoden YourEnum.None als eines der Flags zurückgeben, wenn Sie ein Flag mit dem Wert Null haben, das "None" darstellen sollte auch wenn es nicht in der Aufzählung ist, auf der Sie die Methode ausführen! Ich bekam seltsame doppelte Protokolleinträge, weil Methoden öfter ausgeführt wurden als erwartet, als nur ein Enum-Flag ungleich Null gesetzt war. Vielen Dank, dass Sie sich die Zeit genommen haben, diese großartige Lösung zu aktualisieren und hinzuzufügen!
BrianH
yield return bits;?
Jaider
1
Ich wollte eine Enum-Variable "All", für die ich ulong.MaxValue zugewiesen habe, daher werden alle Bits auf '1' gesetzt. Ihr Code trifft jedoch auf eine Endlosschleife, da Flag <Bits niemals als wahr ausgewertet werden (Flag-Schleifen werden negativ und bleiben dann bei 0 hängen).
Haighstrom
15

Wenn Sie die Methode von @ Greg verlassen, aber eine neue Funktion aus C # 7.3 hinzufügen, lautet die EnumEinschränkung:

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

Die neue Einschränkung ermöglicht es, dass dies eine Erweiterungsmethode ist, ohne dass sie durchgearbeitet werden muss (int)(object)e, und ich kann die HasFlagMethode verwenden und direkt von Taus umwandeln value.

C # 7.3 fügte auch Einschränkungen für Verzögerungen und hinzu unmanaged.

AustinWBryan
quelle
4
Sie wollten wahrscheinlich, dass der flagsParameter auch vom generischen Typ Tist, andernfalls müssten Sie den Aufzählungstyp bei jedem Aufruf explizit angeben.
Ray
11

+1 für die Antwort von @ RobinHood70. Ich fand, dass eine generische Version der Methode für mich praktisch war.

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 Und +1 für @AustinWBryan, um C # 7.3 in den Lösungsraum zu bringen.

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;
        }
    }
}
Wallace Kelly
quelle
3

Sie müssen nicht alle Werte iterieren. Überprüfen Sie einfach Ihre spezifischen Flags wie folgt:

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

oder (wie pstrjds in den Kommentaren sagte) Sie können überprüfen, ob Sie es verwenden, wie:

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}
Dr. TJ
quelle
5
Wenn Sie .Net 4.0 verwenden, gibt es eine Erweiterungsmethode HasFlag, mit der Sie dasselbe tun können: myVar.HasFlag (FlagsEnum.Flag1)
pstrjds
1
Wenn ein Programmierer eine bitweise UND-Operation nicht verstehen kann, sollte er sie zusammenpacken und eine neue Karriere finden.
Ed S.
2
@ Ed: wahr, aber HasFlag ist besser, wenn Sie wieder Code lesen ... (vielleicht nach einigen Monaten oder Jahren)
Dr. TJ
4
@ Ed Swangren: Es geht wirklich darum, den Code lesbarer und weniger ausführlich zu machen, nicht unbedingt, weil die Verwendung bitweiser Operationen "schwierig" ist.
Jeff Mercado
2
HasFlag ist enorm langsam. Wenn Sie eine große Schleife mit HasFlag oder Bitmaskierung versuchen, werden Sie einen großen Unterschied feststellen.
3

Ich habe meinen Ansatz geändert, anstatt den Eingabeparameter der Methode als enumTyp einzugeben , habe ich ihn als Array vom enumTyp ( MyEnum[] myEnums) eingegeben. Auf diese Weise durchlaufe ich das Array einfach mit einer switch-Anweisung innerhalb der Schleife.

Ragin'Geek
quelle
2

War mit den obigen Antworten nicht zufrieden, obwohl sie der Anfang waren.

Nachdem Sie hier einige verschiedene Quellen zusammengesetzt haben:
Vorheriges Poster im SO QnA-
Code-Projekt Enum Flags dieses Threads Check Post
Great Enum <T> Utility

Ich habe das erstellt, also lass mich wissen, was du denkst.
Parameter ::
bool checkZeroWeist es an, 0als Flag-Wert zuzulassen . Standardmäßig wird input = 0leer zurückgegeben.
bool checkFlags: weist es an zu prüfen, ob das Enummit dem [Flags]Attribut dekoriert ist .
PS. Ich habe momentan keine Zeit, die checkCombinators = falseAlge herauszufinden, die sie dazu zwingt, alle Enum-Werte zu ignorieren, die Kombinationen von Bits sind.

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

    }

Hierbei wird die hier gefundene Helper Class Enum <T> verwendet, die ich aktualisiert habe, um sie zu verwenden yield returnfür 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);
    }
}  

Zum Schluss noch ein Beispiel für die Verwendung:

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

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

        return cts;
    }
Eudaimos
quelle
Entschuldigung, ich hatte seit einigen Tagen keine Zeit mehr, mir dieses Projekt anzuschauen. Ich melde mich bei Ihnen, sobald ich mir Ihren Code genauer angesehen habe.
4
Nur ein Hinweis, dass dieser Code einen Fehler enthält. Der Code in beiden Zweigen der if-Anweisung (checkCombinators) ist identisch. Vielleicht ist es auch kein Fehler, aber unerwartet, dass ein für 0 deklarierter Aufzählungswert immer in der Sammlung zurückgegeben wird. Es scheint, dass dies nur zurückgegeben werden sollte, wenn checkZero true ist und keine anderen Flags gesetzt sind.
Dhochee
@dhochee. Genau. Oder der Code ist ziemlich nett, aber die Argumente sind verwirrend.
Nach dem
2

Aufbauend auf Gregs obiger Antwort wird auch der Fall behandelt, in dem Sie einen Wert 0 in Ihrer Aufzählung haben, z. B. None = 0. In diesem Fall sollte dieser Wert nicht durchlaufen werden.

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

Würde jemand wissen, wie man dies noch weiter verbessern kann, damit es den Fall behandeln kann, in dem alle Flags in der Aufzählung auf eine super intelligente Weise gesetzt sind, die alle zugrunde liegenden Aufzählungstypen und den Fall von All = ~ 0 und All = EnumValue1 | behandeln kann EnumValue2 | EnumValue3 | ...

Didier A.
quelle
1

Sie können einen Iterator aus der Aufzählung verwenden. Ausgehend vom MSDN-Code:

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

Es ist nicht getestet. Wenn ich also einen Fehler gemacht habe, können Sie mich gerne anrufen. (Offensichtlich ist es kein Wiedereintritt.) Sie müssten den Wert vorher zuweisen oder ihn in eine andere Funktion aufteilen, die enum.dayflag und enum.days verwendet. Möglicherweise können Sie mit der Gliederung irgendwohin gehen.

SilverbackNet
quelle
0

Es könnte auch der folgende Code sein:

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(',');
}

Es geht nur dann hinein, wenn der Aufzählungswert im Wert enthalten ist

G. Manucci
quelle
0

Alle Antworten funktionieren gut mit einfachen Flags. Wenn Sie Flags kombinieren, werden Sie wahrscheinlich auf Probleme stoßen.

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

Möglicherweise muss eine Prüfung hinzugefügt werden, um zu testen, ob der Enum-Wert selbst eine Kombination ist. Sie würden wahrscheinlich etwas brauchen, wie es hier von Henk van Boeijen gepostet wurde , um Ihren Bedarf zu decken (Sie müssen ein wenig nach unten scrollen)

Walter Vehoeven
quelle
0

Erweiterungsmethode unter Verwendung der neuen Enum-Einschränkung und der neuen Generika, um das Casting zu verhindern:

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();
    }
}
Saeb Amini
quelle
-1

Sie können dies direkt tun, indem Sie in int konvertieren, aber Sie verlieren die Typprüfung. Ich denke, der beste Weg ist, etwas Ähnliches wie meinen Vorschlag zu verwenden. Es behält den richtigen Typ den ganzen Weg. Keine Konvertierung erforderlich. Es ist nicht perfekt, weil das Boxen die Leistung ein wenig beeinträchtigt.

Nicht perfekt (Boxen), aber es macht den Job ohne Vorwarnung ...

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

Verwendung:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }
Eric Ouellet
quelle