web-dev-qa-db-fra.com

Itérer sur les valeurs dans Flags Enum?

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

113
RobinHood70

Revenons à cela 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 il y a quelques années, j'utilise HasFlag ici, car il est beaucoup plus lisible que les comparaisons entre bits et que 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 autant que je sache ... je n'ai pas testé.)

25
RobinHood70
static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}
165
Greg

Il n'y a pas de méthode AFAIK pour obtenir chaque composant. Voici une façon dont vous pouvez 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 que Enum fait en interne pour générer la chaîne afin de renvoyer les indicateurs. 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érale 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
39
Jeff Mercado

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

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

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

Hors de la méthode de @ Greg, mais en ajoutant une nouvelle fonctionnalité à partir de C # 7.3, la contrainte Enum

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 à cette méthode d'être une méthode d'extension, sans avoir à transtyper avec (int)(object)e, et je peux utiliser la méthode HasFlag et transtyper directement en T à partir de value.

C # 7.3 ajoutait aussi des contraintes à for delagates et unmanaged.

6
AustinWBryan

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

Après avoir rassemblé différentes sources ici:
Affiche précédente dans SO QnA
Code Project Enum Flags Check Post
Great Enum <T> Utility

J'ai créé cela alors laissez-moi savoir ce que vous pensez.
Paramètres:
bool checkZero: lui dit d'autoriser 0 en tant que valeur d'indicateur. Par défaut, input = 0 renvoie vide.
bool checkFlags: lui dit de vérifier si la Enum est décorée avec l'attribut [Flags].
PS. Je n'ai pas le temps pour l'instant de comprendre le checkCombinators = false alg qui l'obligera à ignorer toutes les valeurs enum 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;
            }
        }

    }

Ceci utilise la classe Helper Enum <T> trouvée ici que j'ai mise à jour pour utiliser yield return pour 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;
    }
3
eudaimos

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 enum, telle que Aucune = 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;
}

Est-ce que quelqu'un saurait comment améliorer encore cela afin qu'il puisse gérer le cas où tous les indicateurs de l'énumération sont définis de manière très intelligente et qui peuvent gérer tous les types d'énum sous-jacents et le cas de All = ~ 0 et All = EnumValue1 | EnumValue2 | EnumValue3 | ...

2
Didier A.

Vous n'avez pas besoin de parcourir toutes les valeurs. Il suffit de vérifier vos drapeaux spécifiques comme ceci:

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

ou (comme pstrjds dit dans les commentaires), vous pouvez vérifier si vous l'utilisez comme:

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}
2
Dr TJ

Au lieu de taper le paramètre d'entrée de la méthode sous le type enum, je l'ai saisi sous la forme d'un tableau du type enum (MyEnum[] myEnums). Ainsi, je parcourais simplement le tableau avec une instruction switch à l'intérieur du boucle.

2
Ragin'Geek

Vous pouvez utiliser un itérateur de l'énum. À 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é, alors si je fais une erreur, n'hésitez pas à m'appeler. (Évidemment, il ne s'agit pas d'une ré-entrée.) Vous devez attribuer une valeur à l'avance ou la diviser en une autre fonction utilisant enum.dayflag et enum.days. Vous pourriez peut-être aller quelque part avec le contour.

1
SilverbackNet

Cela pourrait être aussi bien 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 va à l'intérieur si seulement quand la valeur enum est contenue dans la valeur

0
G. Manucci

Toutes les réponses fonctionnent bien avec de simples drapeaux, vous allez probablement entrer dans des problèmes lorsque les drapeaux 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 vérifier si la valeur enum elle-même est une combinaison. Vous aurez probablement besoin de quelque chose comme posté ici par Henk van Boeijen pour couvrir votre besoin (vous devez faire défiler un peu)