Comment vérifier si des indicateurs d'une combinaison d'indicateurs sont définis?

180

Disons que j'ai cette énumération:

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

Pour vérifier si, par exemple, ABest défini, je peux le faire:

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

Existe-t-il un moyen plus simple de vérifier si l'un des indicateurs d'une constante d'indicateur combiné est défini que le suivant?

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

Pourriez-vous par exemple échanger le &avec quelque chose?

Pas trop stable quand il s'agit de trucs binaires comme ça ...

Svish
la source
Tout le monde ne devrait pas lire «All = A | B | C '?
stevehipwell
4
AB | C est équivalent à A | B | C parce que AB a été défini comme A | B avant.
Daniel Brückner
1
@Daniel Brückner - C'est équivalent, mais c'est moins lisible. Surtout si l'énumération a été développée.
stevehipwell
Vrai. Je peux le changer pour une meilleure lecture.
Svish

Réponses:

145

Si vous voulez savoir si une lettre contient l'une des lettres AB, vous devez utiliser l' opérateur AND &. Quelque chose comme:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}
yeyeyerman
la source
2
Autant que je puisse voir, cela fait le travail. Et a eu les commentaires les plus clairs. Ne compile pas sans parenthèse letter & Letters.AB. J'ai édité ça là-dedans.
Svish
De plus, si j'introduis un Letters.None, je suppose que vous pourriez l'échanger avec le 0pour un look moins comparé au nombre magique?
Svish
Bien sûr. Mais je ne pense pas que la comparaison ET avec 0 puisse être considérée comme un nombre magique strictement.
yeyeyerman
9
également stackoverflow.com/questions/40211/how-to-compare-flags-in-c est une réponse recommandée car il vérifie par rapport à l'élément en question plutôt que de vérifier s'il est égal à 0
dan richardson
@danrichardson le problème avec la vérification de l'élément exact est qu'elle élimine le cas où une partie de la valeur composée est définie (soit A, soit B), ce qui n'est pas ce que l'OP veut.
Tom Lint
181

Dans .NET 4, vous pouvez utiliser la méthode 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);   
   }
}

L'exemple affiche la sortie suivante:

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.
Chuck Kasabula
la source
14
Cela ne répond pas à la question du PO. Vous devez toujours && plusieurs opérations HasFlag pour déterminer si des indicateurs sont définis. La question est donc de savoir petsInFamilysi un Pet.Dog || Pet.Cat?
GoClimbColorado
1
Voir la réponse claire de M. Skeet ... HasFlags Multiple
GoClimbColorado
59

J'utilise des méthodes d'extension pour écrire des choses comme ça:

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

Voici le 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
la source
1
Vous pouvez faire un peu plus serré comme ceci: where T : struct, IConvertible. Excellent code sinon!
Hamish Grubijan
@HamishGrubijan, bon point ... et les énumérations implémentent également IFormattable et IComparable. Cependant, tous les types numériques implémentent également ces interfaces, il ne suffit donc pas de les exclure
Thomas Levesque
Merci pour le partage, mais vous n'avez pas toujours besoin de vérifier l'énumération. IsFlagSet(this Enum value, Enum flag)est suffisant.
djmj le
34

Il existe une méthode HasFlag dans .NET 4 ou supérieur.

if(letter.HasFlag(Letters.AB))
{
}
Artru
la source
26

Si vous pouvez utiliser .NET 4 ou supérieur, utilisez la méthode HasFlag ()

exemples

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

pareil que

letter.HasFlag(Letters.AB)
Luka
la source
Etes-vous sûr bitwise ORqu'il "les deux doivent être définis" et non aucun?
Brackets
1
bitwise ORcombinerait les valeurs, donc 1000 | 0010 devient 1010, ou les deux ensemble
Armando
13

Si cela vous ennuie vraiment, vous pouvez écrire une fonction comme celle-ci:

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
la source
1
La ligne return (value & flag) == flag;ne compile pas: "L'opérateur '&' ne peut pas être appliqué aux opérandes de type 'T' et 'T'" .
Fredrik Mörk
1
awe: La question ne concernait pas les opérations binaires, la question était de simplifier la syntaxe des opérations liées aux masques de bits en C #. Il y a déjà beaucoup d'excellentes questions et réponses liées aux opérations binaires sur stackoverflow, il n'est pas nécessaire de les republier partout.
Tamas Czinege
Je devrais recommander que ceux qui ne connaissent pas les opérations binaires se familiarisent, car l'échafaudage pour le cacher ci-dessus rend en fait les choses beaucoup moins lisibles à mon avis. Bien sûr, ma solution `` brute '' ci-dessous ne fonctionne pas si bien actuellement par rapport au score de cette solution, donc les gens votent leurs préférences et je dois respecter cela ;-)
Sera
10

Pour vérifier si, par exemple, AB est défini, je peux le faire:

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

Existe-t-il un moyen plus simple de vérifier si l'un des indicateurs d'une constante d'indicateur combiné est défini que le suivant?

Cela vérifie que les deux A et B sont définis, et ne tient pas si d'autres drapeaux sont ensemble.

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

Cela vérifie que soit A ou B est définie, et ne tient pas si d'autres indicateurs sont définis ou non.

Cela peut être simplifié en:

if(letter & Letters.AB)

Voici le C pour les opérations binaires; il devrait être simple de l'appliquer à 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));

Incidemment, la dénomination de la variable dans l'exemple de la question est la «lettre» singulière, ce qui pourrait impliquer qu'elle ne représente qu'une seule lettre; L'exemple de code indique clairement qu'il s'agit d'un ensemble de lettres possibles et que plusieurs valeurs sont autorisées, alors pensez à renommer la variable «lettres».

Volonté
la source
Ne serait pas anything_and_a, a_and_or_c_and_anything_elseet both_ac_and_anything_elsesera toujours vrai? ou est-ce que je manque quelque chose ici?
Svish
Dans ce cas, vous pouvez voir sur quels indicateurs ont été initialisés. Cependant, si les drapeaux ne contiennent pas A, alors (flags & A) serait 0, ce qui est faux. both_ac_and_anything_else garantit que A et C sont tous les deux définis, mais ignore tous les autres indicateurs également définis (par exemple, il est vrai que B soit défini ou non).
Sera
Hm, certains d'entre eux finissent par être des nombres et non des booléens en C #. Comment les convertiriez-vous en booléen?
Svish
Ce n'est pas implicitement converti pour vous? Zéro équivaut à «faux» et toutes les autres valeurs sont «vraies».
Sera
4

Que diriez-vous

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

?

Jakob Christensen
la source
Oui! Cela filtrera sur les valeurs A et B et ignorera si C est inclus. Donc, s'il est> 0, c'est aussi A ou B ou AB.
awe
3
Cela ne fonctionne pas à 100% avec des valeurs signées. ! = 0 est meilleur que> 0 pour cette raison.
stevehipwell
4

J'ai créé une méthode d'extension simple qui ne nécessite pas de vérification des Enumtypes:

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

Cela fonctionne également sur les énumérations nullables. La HasFlagméthode standard ne le fait pas, j'ai donc créé une extension pour couvrir cela aussi.

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

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

Un test simple:

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

Prendre plaisir!

Henk van Boeijen
la source
4

Il y a beaucoup de réponses ici mais je pense que la façon la plus idiomatique de le faire avec Flags serait Letters.AB.HasFlag (lettre) ou (Letters.A | Letters.B) .HasFlag (lettre) si vous ne l'avez pas fait ont déjà Letters.AB. letter.HasFlag (Letters.AB) ne fonctionne que s'il a les deux.

Novaterata
la source
3

Cela fonctionnerait-il pour vous?

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

Cordialement,

Sebastiaan

Sebastiaan M
la source
1

Vous pouvez utiliser cette méthode d'extension sur enum, pour tout type d'énumérations:

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
la source
0
if((int)letter != 0) { }
Lee
la source
Vous pourriez faire la même erreur que moi - il veut vérifier si A ou B est défini mais ignorer C.
Daniel Brückner
Vous n'avez pas besoin du casting si vous
comparez
Cela vérifierait si l'un d'entre eux était défini, pas si une énumération combinée était définie.
Svish
0

Vous pouvez simplement vérifier si la valeur n'est pas zéro.

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

Mais je considérerais que c'est une meilleure solution d'introduire une nouvelle valeur d'énumération avec la valeur zéro et de comparer à nouveau cette valeur d'énumération (si possible, car vous devez pouvoir modifier l'énumération).

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

if (letter != Letters.None) { }

METTRE À JOUR

Mauvaise lecture de la question - correction de la première suggestion et ignorer simplement la deuxième suggestion.

Daniel Brückner
la source
Vous n'avez pas besoin du casting si vous
comparez
0

Il y a deux approches que je peux voir qui fonctionneraient pour vérifier n'importe quel bit en cours de définition.

Approche A

if (letter != 0)
{
}

Cela fonctionne tant que cela ne vous dérange pas de vérifier tous les bits, y compris les bits non définis!

Approche B

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

Cela ne vérifie que les bits définis, tant que Letters.All représente tous les bits possibles.

Pour des bits spécifiques (un ou plusieurs ensembles), utilisez Aproach B en remplaçant Letters.Tous par les bits que vous souhaitez vérifier (voir ci-dessous).

if ((letter & Letters.AB) != 0)
{
}
Stevehipwell
la source
Vous pourriez faire la même erreur que moi - il veut vérifier si A ou B est défini mais ignorer C.
Daniel Brückner
-1

Désolé, mais je vais le montrer en 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 Donc l'énumération contient 1 + 4

Wiroko
la source