Comment parcourir les valeurs d'un Enum ayant des indicateurs?

131

Si j'ai une variable contenant une énumération d'indicateurs, puis-je en quelque sorte itérer sur les valeurs de bits dans cette variable spécifique? Ou dois-je utiliser Enum.GetValues ​​pour parcourir l'ensemble de l'énumération et vérifier lesquels sont définis?

Olivier Rogier
la source
Si vous contrôlez votre API, évitez d'utiliser des indicateurs de bits. Ils constituent rarement une optimisation utile. L'utilisation d'une structure avec plusieurs champs 'booléens' publics est sémantiquement équivalente, mais votre code est considérablement plus simple. Et si vous en avez besoin plus tard, vous pouvez modifier les champs en propriétés qui manipulent les champs de bits en interne, encapsulant l'optimisation.
Jay Bazuzi
2
Je vois ce que vous dites, et dans de nombreux cas, cela aurait du sens, mais dans ce cas, j'aurais le même problème que la suggestion If ... je devrais écrire des instructions If pour une douzaine de booléens différents à la place d'utiliser une simple boucle foreach sur un tableau. (Et comme cela fait partie d'une DLL publique, je ne peux pas faire des choses obscures comme avoir simplement un tableau de booléens ou quoi que ce soit d'autre.)
10
"votre code est considérablement plus simple" - tout le contraire est vrai si vous faites autre chose que tester des bits individuels ... les boucles et les opérations d'ensemble deviennent pratiquement impossibles car les champs et les propriétés ne sont pas des entités de première classe.
Jim Balter
2
@nawfal "un peu" Je vois ce que tu as fait là-bas
stannius

Réponses:

179
static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}
Greg
la source
7
Notez qu'il HasFlagest disponible à partir de .NET 4.
Andreas Grech
3
C'est bien! Mais vous pouvez le rendre encore plus simple et facile à utiliser. Il suffit de coller ceci comme méthode d'extension: Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag); Ensuite, juste: myEnum.GetFLags():)
joshcomley
3
Excellent one-liner, josh, mais il souffre toujours du problème de ramasser des valeurs multi-flag (Boo) au lieu de seulement des valeurs à un seul drapeau (Bar, Baz), comme dans la réponse de Jeff, ci-dessus.
10
Bien - faites attention à None - par exemple, les éléments Aucune de la réponse de Jeff ne sera toujours incluse
Ilan
1
La signature de la méthode devrait êtrestatic IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers
48

Voici une solution Linq au problème.

public static IEnumerable<Enum> GetFlags(this Enum e)
{
      return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}
agritton
la source
7
Pourquoi n'est-ce pas au sommet? :) À utiliser .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));si vous avez une valeur nulle à représenterNone
georgiosd
Super propre. Meilleure solution à mon avis.
Ryan Fiorini
@georgiosd Je suppose que la performance n'est pas terrible. (Mais devrait être assez bon pour la plupart des tâches)
AntiHeadshot
41

Il n'y a pas de méthodes intégrées pour obtenir chaque composant pour autant que je sache. Mais voici une façon de les obtenir:

[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

J'ai adapté ce qui Enumfait en interne pour générer la chaîne pour renvoyer à la place les drapeaux. Vous pouvez regarder le code dans le réflecteur et devrait être plus ou moins équivalent. Fonctionne bien pour les cas d'utilisation généraux où il existe des valeurs contenant plusieurs bits.

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

La méthode d'extension GetIndividualFlags()obtient tous les indicateurs individuels pour un type. Les valeurs contenant plusieurs bits sont donc omises.

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz
Jeff Mercado
la source
J'avais envisagé de faire un fractionnement de chaîne, mais c'est probablement beaucoup plus de temps que de simplement itérer les valeurs de bits de l'énumération entière.
Malheureusement, ce faisant, vous devrez tester les valeurs redondantes (si vous ne les vouliez pas). Voir mon deuxième exemple, il céderait Bar, Bazet Booau lieu de juste Boo.
Jeff Mercado
Intéressant que vous puissiez sortir Boo de ça, bien que cette partie soit inutile (et en fait, une très mauvaise idée :)) pour ce que je fais.
Cela semble intéressant, mais si je ne me trompe pas, cela renverrait Boo pour l'énumération ci-dessus, où je voudrais énumérer uniquement les versions qui ne sont pas des combinaisons d'autres valeurs (c'est-à-dire celles qui sont des puissances de deux) . Cela pourrait-il être fait facilement? J'ai essayé et je ne peux pas penser à un moyen facile d'identifier cela sans recourir aux mathématiques de la PF.
@Robin: Vous avez raison, l'original renverrait Boo(la valeur renvoyée avec ToString()). Je l'ai modifié pour n'autoriser que les indicateurs individuels. Donc, dans mon exemple, vous pouvez obtenir Baret Bazau lieu de Boo.
Jeff Mercado
26

Pour en revenir quelques années plus tard, avec un peu plus d'expérience, ma réponse ultime pour les valeurs à un seul bit, passant du bit le plus bas au bit le plus élevé, est une légère variante de la routine interne de Jeff Mercado:

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

Cela semble fonctionner, et malgré mes objections d'il y a quelques années, j'utilise HasFlag ici, car c'est beaucoup plus lisible que d'utiliser des comparaisons au niveau du bit et la différence de vitesse est insignifiante pour tout ce que je vais faire. (Il est tout à fait possible qu'ils aient amélioré la vitesse de HasFlags depuis de toute façon, pour tout ce que je sais ... je n'ai pas testé.)


la source
Juste un indicateur de note est initialisé à un entier devrait être à un ULONG tels que les bits, doit être initialisé comme 1UL
forcewill
Merci, je vais réparer ça! (Je suis juste allé vérifier mon code réel et je l'avais déjà corrigé dans l'autre sens en le déclarant spécifiquement comme un ulong.)
2
C'est la seule solution que j'ai trouvée qui ne semble pas non plus souffrir du fait que si vous avez un drapeau avec la valeur zéro, qui devrait représenter "Aucun", les autres méthodes GetFlag () de réponses renverront le YourEnum.None comme l'un des indicateurs même si ce n'est pas réellement dans l'énumération sur laquelle vous exécutez la méthode! J'obtenais d'étranges entrées de journal en double parce que les méthodes s'exécutaient plus souvent que prévu lorsqu'elles n'avaient qu'un seul indicateur d'énumération non nul. Merci d'avoir pris le temps de mettre à jour et d'ajouter cette excellente solution!
BrianH
yield return bits;?
Jaider
1
Je voulais une variable d'énumération "All", pour laquelle j'ai assigné ulong.MaxValue, donc tous les bits sont mis à "1". Mais votre code atteint une boucle infinie, car flag <bits ne s'évalue jamais à vrai (le drapeau boucle à négatif puis reste bloqué à 0).
Haighstrom
15

En partant de la méthode de @ Greg, mais en ajoutant une nouvelle fonctionnalité à partir de C # 7.3, la Enumcontrainte:

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

La nouvelle contrainte permet à cela d'être une méthode d'extension, sans avoir à effectuer de conversion (int)(object)e, et je peux utiliser la HasFlagméthode et convertir directement à Tpartir de value.

C # 7.3 a également ajouté des contraintes pour les délicats et unmanaged.

AustinWBryan
la source
4
Vous vouliez probablement que le flagsparamètre soit également de type générique T, sinon vous devrez spécifier explicitement le type enum chaque fois que vous l'appelez.
Ray
11

+1 pour la réponse fournie par @ RobinHood70. J'ai trouvé qu'une version générique de la méthode me convenait.

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 Et +1 pour @AustinWBryan pour avoir introduit C # 7.3 dans l'espace de solution.

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
la source
3

Vous n'avez pas besoin d'itérer toutes les valeurs. vérifiez simplement vos drapeaux spécifiques comme ceci:

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

ou (comme pstrjds l'a dit dans les commentaires), vous pouvez vérifier son utilisation comme:

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}
Dr TJ
la source
5
Si vous utilisez .Net 4.0, il existe une méthode d'extension HasFlag que vous pouvez utiliser pour faire la même chose: myVar.HasFlag (FlagsEnum.Flag1)
pstrjds
1
Si un programmeur ne peut pas comprendre une opération AND au niveau du bit, il doit la préparer et trouver une nouvelle carrière.
Ed S.
2
@Ed: vrai, mais HasFlag est meilleur lorsque vous lisez à nouveau du code ... (peut-être après quelques mois ou années)
Dr TJ
4
@Ed Swangren: Il s'agit vraiment de rendre le code plus lisible et moins verbeux, pas nécessairement parce que l'utilisation d'opérations au niveau du bit est "difficile".
Jeff Mercado
2
HasFlag est extrêmement lent. Essayez une grande boucle en utilisant HasFlag par rapport au masquage de bits et vous remarquerez une énorme différence.
3

Ce que j'ai fait, c'est changer mon approche, au lieu de taper le paramètre d'entrée de la méthode comme enumtype, je l'ai tapé comme un tableau du enumtype ( MyEnum[] myEnums), de cette façon, je viens de parcourir le tableau avec une instruction switch à l'intérieur de la boucle.

Ragin'Geek
la source
2

N'était pas satisfait des réponses ci-dessus, même si elles étaient le début.

Après avoir rassemblé quelques sources différentes ici:
Affiche précédente dans le projet de code SO QnA de ce fil
Enum Flags Check Post
Great Enum <T> Utility

J'ai créé ceci alors laissez-moi savoir ce que vous en pensez.
Paramètres:: lui
bool checkZerodit d'autoriser 0comme valeur d'indicateur. Par défaut, input = 0retourne vide.
bool checkFlags: lui dit de vérifier si le Enumest décoré avec l' [Flags]attribut.
PS. Je n'ai pas le temps pour le moment de comprendre l' checkCombinators = falsealg qui le forcera à ignorer les valeurs d'énumération qui sont des combinaisons de bits.

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

    }

Cela utilise la classe Helper Enum <T> trouvée ici que j'ai mise à jour yield returnpour 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);
    }
}  

Enfin, voici un exemple d'utilisation:

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

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

        return cts;
    }
eudaimos
la source
Désolé, je n'ai pas eu le temps de regarder ce projet depuis plusieurs jours. Je vous recontacterai une fois que j'aurai un meilleur aperçu de votre code.
4
Juste un avertissement qu'il y a un bogue dans ce code. Le code des deux branches de l'instruction if (checkCombinators) est identique. Aussi, peut-être pas un bogue, mais inattendu, c'est que si vous avez une valeur enum déclarée pour 0, elle sera toujours retournée dans la collection. Il semble que cela ne devrait être retourné que si checkZero est vrai et qu'aucun autre indicateur n'est défini.
dhochee le
@dhochee. Je suis d'accord. Ou le code est plutôt sympa, mais les arguments sont déroutants.
AFract
2

En s'appuyant sur la réponse de Greg ci-dessus, cela prend également en charge le cas où vous avez une valeur 0 dans votre énumération, telle que None = 0. Dans ce cas, il ne devrait pas itérer sur cette valeur.

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

Quelqu'un saurait-il comment améliorer encore davantage cela afin qu'il puisse gérer le cas où tous les indicateurs de l'énumération sont définis d'une manière super intelligente qui pourrait gérer tous les types enum sous-jacents et le cas de All = ~ 0 et All = EnumValue1 | EnumValue2 | EnumValue3 | ...

Didier A.
la source
1

Vous pouvez utiliser un itérateur de Enum. À partir du code MSDN:

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

Ce n'est pas testé, donc si j'ai fait une erreur, n'hésitez pas à m'appeler. (évidemment ce n'est pas rentrant.) Vous devrez attribuer une valeur au préalable, ou la décomposer dans une autre fonction qui utilise enum.dayflag et enum.days. Vous pourrez peut-être aller quelque part avec le contour.

SilverbackNet
la source
0

Cela pourrait être ainsi que le code suivant:

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

Il entre à l'intérieur si ce n'est que lorsque la valeur enum est contenue dans la valeur

G. Manucci
la source
0

Toutes les réponses fonctionnent bien avec des indicateurs simples, vous allez probablement avoir des problèmes lorsque les indicateurs sont combinés.

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

probablement besoin d'ajouter une vérification pour tester si la valeur enum elle-même est une combinaison. Vous auriez probablement besoin de quelque chose comme publié ici par Henk van Boeijen pour couvrir vos besoins (vous devez faire défiler un peu vers le bas)

Walter Vehoeven
la source
0

Méthode d'extension utilisant la nouvelle contrainte Enum et les génériques pour empêcher la conversion:

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
la source
-1

Vous pouvez le faire directement en convertissant en int mais vous perdrez la vérification de type. Je pense que la meilleure façon est d'utiliser quelque chose de similaire à ma proposition. Il garde le bon type tout le temps. Aucune conversion requise. Ce n'est pas parfait à cause de la boxe qui ajoutera un peu de succès aux performances.

Pas parfait (boxe), mais ça fait le boulot sans avertissement ...

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

Usage:

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