Que signifie l'attribut [Flags] Enum en C #?

1447

De temps en temps, je vois une énumération comme celle-ci:

[Flags]
public enum Options 
{
    None    = 0,
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8
}

Je ne comprends pas exactement ce que fait l' [Flags]attribut.

Quelqu'un at-il une bonne explication ou un exemple qu'il pourrait publier?

Brian Leahy
la source
Il convient également de noter, en plus de la réponse acceptée, que VB.NET nécessite en fait [Flags] - au moins selon les gars de .NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/…
Rushyo
8
Remarque, non requis dans VB ces jours-ci. Enregistrer le comportement en C # - modifie simplement la sortie ToString (). Remarque, vous pouvez également faire un OU logique, DANS L'ENUM lui-même. Très cool. Cat = 1, Dog = 2, CatAndDog = Cat || Chien.
Chalky
14
@ Chalky Vous voulez dire CatAndDog = Cat | Dog(le OU logique au lieu du Conditionnel), je suppose?
DdW
5
@DdW, partiellement correct: | doit être utilisé, mais | est appelé OU binaire. II est le OU logique (qui permet de court-circuiter): au moins selon Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx
Pieter21

Réponses:

2154

L' [Flags]attribut doit être utilisé chaque fois que l'énumérable représente une collection de valeurs possibles, plutôt qu'une seule valeur. Ces collections sont souvent utilisées avec des opérateurs au niveau du bit, par exemple:

var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Notez que l' [Flags]attribut ne permet pas cela par lui-même - tout ce qu'il fait est de permettre une belle représentation par la .ToString()méthode:

enum Suits { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }
[Flags] enum SuitsFlags { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }

...

var str1 = (Suits.Spades | Suits.Diamonds).ToString();
           // "5"
var str2 = (SuitsFlags.Spades | SuitsFlags.Diamonds).ToString();
           // "Spades, Diamonds"

Il est également important de noter que les valeurs d'énumération [Flags] ne sont pas automatiquement égales à deux. Si vous omettez les valeurs numériques, l'énumération ne fonctionnera pas comme on pourrait s'y attendre dans les opérations au niveau du bit, car par défaut, les valeurs commencent par 0 et incrémentent.

Déclaration incorrecte:

[Flags]
public enum MyColors
{
    Yellow,  // 0
    Green,   // 1
    Red,     // 2
    Blue     // 3
}

Les valeurs, si elles sont déclarées de cette façon, seront Jaune = 0, Vert = 1, Rouge = 2, Bleu = 3. Cela le rendra inutile en tant que drapeaux.

Voici un exemple de déclaration correcte:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}

Pour récupérer les valeurs distinctes de votre propriété, on peut le faire:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow is allowed...
}

ou avant .NET 4:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow is allowed...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green is allowed...
}    

Sous les couvertures

Cela fonctionne parce que vous avez utilisé des pouvoirs de deux dans votre énumération. Sous les couvertures, vos valeurs d'énumération ressemblent à ceci en binaires et en zéros:

 Yellow: 00000001
 Green:  00000010
 Red:    00000100
 Blue:   00001000

De même, une fois que vous avez défini votre propriété AllowedColors sur Rouge, Vert et Bleu à l'aide de l' |opérateur binaire OR au niveau du bit , AllowedColors ressemble à ceci:

myProperties.AllowedColors: 00001110

Ainsi, lorsque vous récupérez la valeur que vous effectuez réellement au niveau du bit ET &sur les valeurs:

myProperties.AllowedColors: 00001110
             MyColor.Green: 00000010
             -----------------------
                            00000010 // Hey, this is the same as MyColor.Green!

La valeur None = 0

Et concernant l'utilisation de 0dans votre énumération, citant MSDN:

[Flags]
public enum MyColors
{
    None = 0,
    ....
}

Utilisez None comme nom de la constante énumérée d'indicateur dont la valeur est zéro. Vous ne pouvez pas utiliser la constante énumérée None dans une opération AND au niveau du bit pour tester un indicateur car le résultat est toujours zéro. Cependant, vous pouvez effectuer une comparaison logique, et non pas au niveau du bit, entre la valeur numérique et la constante énumérée Aucune pour déterminer si des bits de la valeur numérique sont définis.

Vous pouvez trouver plus d'informations sur l'attribut flags et son utilisation sur msdn et sur la conception de flags sur msdn

andnil
la source
156
Les drapeaux eux-mêmes ne font rien. En outre, C # ne nécessite pas de drapeaux en soi. Mais la ToStringmise en œuvre de votre ENUM utilise Flags, et ainsi ne Enum.IsDefined, Enum.Parseetc. Essayez de supprimer Drapeaux et regardez le résultat de MyColor.Yellow | MyColor.Red; sans elle, vous obtenez «5», avec des drapeaux, vous obtenez «jaune, rouge». Certaines autres parties du framework utilisent également [Flags] (par exemple, la sérialisation XML).
Ruben
7
// Le jaune a été réglé ..., je trouve un peu trompeur. Rien n'a été défini, cela signifie que Jaune est membre de vos Couleurs Autorisées, peut-être mieux serait // Jaune est autorisé?
Scott Weaver
48
Je préfère utiliser des constantes de la forme A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Beaucoup plus facile à lire, à comprendre, à vérifier visuellement et à modifier.
Nick Westgate
2
@borrrden, enfer ouais! J'ai trouvé ceci: msdn.microsoft.com/en-us/library/system.enum.aspx - voir la partie "Remarques": "Enum est la classe de base pour toutes les énumérations dans le .NET Framework." et "L'énumération n'hérite pas explicitement d'Enum; la relation d'héritage est gérée implicitement par le compilateur." Ainsi, lorsque vous écrivez: public enum bla bla bla - il s'agit d'un type de valeur. Mais, la méthode HasFlag veut que vous lui donniez une instance de System.Enum qui est une classe (type de référence :)
Aleksei Chepovoi
2
Si vous souhaitez exclure un indicateur de l'énumération, utilisez xor, qui est ^ en C #. Donc si c'est le cas myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Vous pouvez faire:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe
780

Vous pouvez aussi faire ça

[Flags]
public enum MyEnum
{
    None   = 0,
    First  = 1 << 0,
    Second = 1 << 1,
    Third  = 1 << 2,
    Fourth = 1 << 3
}

Je trouve le décalage de bits plus facile que de taper 4,8,16,32 et ainsi de suite. Cela n'a aucun impact sur votre code car tout est fait au moment de la compilation

Orion Edwards
la source
18
C'est ce que je veux dire, je préfère avoir l'intégralité du code source. Si j'ai une colonne sur une table dans la base de données appelée MyEnum qui stocke une valeur de l'une des énumérations, et un enregistrement a 131 072, je devrais sortir ma calculatrice pour comprendre que cela correspond à l'énumération avec la valeur 1 << 17. Au lieu de simplement voir la valeur 131 072 écrite dans la source.
JeremyWeir
6
@jwg Je suis d'accord, c'est idiot d'être trop inquiet pour les performances d'exécution de cela, mais tout de même, je pense que c'est bien de savoir que cela n'insérera pas de décalages de bits partout où vous utilisez l'énumération. Plus d'une chose `` qui est soignée '' plutôt que quelque chose lié à la performance
Orion Edwards
18
@JeremyWeir - Plusieurs bits vont être définis dans une valeur d'énumération d'indicateur. Votre méthode d'analyse des données est donc ce qui est inapproprié. Exécutez une procédure pour représenter votre valeur entière en binaire. 131 072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. Avec le 17e bit défini, une valeur d'énumération attribuée 1 << 17 est facilement déterminable. 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. les valeurs d'énumération assignent 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. etc. etc. sont déterminables sans même le l'aide d'une calculatrice du tout .. que je doute que vous seriez en mesure de déterminer du tout sans obtenir le représentant binaire.
Brett Caswell
13
Soulignant également que vous pouvez effectuer un décalage binaire à partir des valeurs d'énumération "précédentes" plutôt que directement à partir de nombres, c'est-à-dire Third = Second << 1plutôt que Third = 1 << 2- voir la description plus complète ci
drzaus
26
Je aime, mais une fois que vous arrivez à la limite supérieure de type ENUM sous - jacente, le compilateur ne prévient pas contre décalages de bits tels que 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2et ainsi de suite. En revanche, si vous dites ThirtySecond = 2147483648pour une énumération de type int, le compilateur génère une erreur.
aaaantoine
115

En combinant les réponses https://stackoverflow.com/a/8462/1037948 (déclaration par décalage de bits) et https://stackoverflow.com/a/9117/1037948 (en utilisant des combinaisons dans la déclaration), vous pouvez plutôt décaler les valeurs précédentes que d'utiliser des chiffres. Pas nécessairement le recommander, mais juste en soulignant que vous pouvez.

Plutôt que:

[Flags]
public enum Options : byte
{
    None    = 0,
    One     = 1 << 0,   // 1
    Two     = 1 << 1,   // 2
    Three   = 1 << 2,   // 4
    Four    = 1 << 3,   // 8

    // combinations
    OneAndTwo = One | Two,
    OneTwoAndThree = One | Two | Three,
}

Vous pouvez déclarer

[Flags]
public enum Options : byte
{
    None    = 0,
    One     = 1 << 0,       // 1
    // now that value 1 is available, start shifting from there
    Two     = One << 1,     // 2
    Three   = Two << 1,     // 4
    Four    = Three << 1,   // 8

    // same combinations
    OneAndTwo = One | Two,
    OneTwoAndThree = One | Two | Three,
}

Confirmation avec LinqPad:

foreach(var e in Enum.GetValues(typeof(Options))) {
    string.Format("{0} = {1}", e.ToString(), (byte)e).Dump();
}

Résulte en:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8
drzaus
la source
23
Les combinaisons sont une bonne recommandation, mais je pense que le décalage de bits enchaîné serait sujet à des erreurs de copier-coller telles que Two = One << 1, Three = One << 1, etc ... Les nombres incrémentiels du forme 1 << n sont plus sûrs et l'intention est plus claire.
Rupert Rawnsley du
2
@RupertRawnsley pour citer ma réponse:> Pas nécessairement la recommander, mais juste en soulignant que vous pouvez
drzaus
49

Veuillez consulter ce qui suit pour un exemple qui montre la déclaration et l'utilisation potentielle:

namespace Flags
{
    class Program
    {
        [Flags]
        public enum MyFlags : short
        {
            Foo = 0x1,
            Bar = 0x2,
            Baz = 0x4
        }

        static void Main(string[] args)
        {
            MyFlags fooBar = MyFlags.Foo | MyFlags.Bar;

            if ((fooBar & MyFlags.Foo) == MyFlags.Foo)
            {
                Console.WriteLine("Item has Foo flag set");
            }
        }
    }
}
JO.
la source
Cet exemple fonctionne même si vous omettez [Drapeaux]. C'est la partie [Flags] sur laquelle j'essaie de me renseigner.
gbarry
39

En extension à la réponse acceptée, en C # 7 les drapeaux enum peuvent être écrits en utilisant des littéraux binaires:

[Flags]
public enum MyColors
{
    None   = 0b0000,
    Yellow = 0b0001,
    Green  = 0b0010,
    Red    = 0b0100,
    Blue   = 0b1000
}

Je pense que cette représentation montre clairement comment les drapeaux fonctionnent sous les couvertures .

Thorkil Holm-Jacobsen
la source
2
Et en C # 7.2, c'est encore plus clair avec le séparateur principal ! 0b_0100
Vincenzo Petronio
37

J'ai demandé récemment quelque chose de similaire.

Si vous utilisez des indicateurs, vous pouvez ajouter une méthode d'extension aux énumérations pour faciliter la vérification des indicateurs contenus (voir l'article pour plus de détails)

Cela vous permet de faire:

[Flags]
public enum PossibleOptions : byte
{
    None = 0,
    OptionOne = 1,
    OptionTwo = 2,
    OptionThree = 4,
    OptionFour = 8,

    //combinations can be in the enum too
    OptionOneAndTwo = OptionOne | OptionTwo,
    OptionOneTwoAndThree = OptionOne | OptionTwo | OptionThree,
    ...
}

Ensuite, vous pouvez faire:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

if( opt.IsSet( PossibleOptions.OptionOne ) ) {
    //optionOne is one of those set
}

Je trouve cela plus facile à lire que la plupart des façons de vérifier les drapeaux inclus.

Keith
la source
IsSet est une méthode d'extension que je suppose?
Robert MacLean
Ouais - lisez l'autre question à laquelle je renvoie pour plus de détails: stackoverflow.com/questions/7244
Keith
71
.NET 4 ajoute une HasFlagméthode aux énumérations, vous pouvez donc vous passer opt.HasFlag( PossibleOptions.OptionOne )de l'écriture de vos propres extensions
Orion Edwards
1
Notez que HasFlagc'est beaucoup plus lent que d'effectuer des opérations au niveau du bit.
Wai Ha Lee
1
@WaiHaLee Vous pouvez utiliser la méthode d'extension CodeJam.EnumHelper.IsFlagSet qui est compilée dans une version rapide en utilisant Expression: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
N__
22

@Nidonocu

Pour ajouter un autre indicateur à un ensemble de valeurs existant, utilisez l'opérateur d'affectation OU.

Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
  && ((Mode & Mode.Read) == Mode.Read)));
steve_c
la source
18

Lorsque je travaille avec des drapeaux, je déclare souvent des éléments supplémentaires Aucun et Tous. Ces informations sont utiles pour vérifier si tous les indicateurs sont définis ou si aucun indicateur n'est défini.

[Flags] 
enum SuitsFlags { 

    None =     0,

    Spades =   1 << 0, 
    Clubs =    1 << 1, 
    Diamonds = 1 << 2, 
    Hearts =   1 << 3,

    All =      ~(~0 << 4)

}

Usage:

Spades | Clubs | Diamonds | Hearts == All  // true
Spades & Clubs == None  // true

 
Mise à jour 2019-10:

Depuis C # 7.0, vous pouvez utiliser des littéraux binaires, qui sont probablement plus intuitifs à lire:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

    Spades =   0b0001, 
    Clubs =    0b0010, 
    Diamonds = 0b0100, 
    Hearts =   0b1000,

    All =      0b1111

}
Jpsy
la source
17

Pour ajouter Mode.Write:

Mode = Mode | Mode.Write;
David Wengier
la source
57
ou Mode | = Mode.Write
abatishchev
14

Il y a quelque chose de trop verbeux dans la if ((x & y) == y)...construction, surtout si xET ysont tous deux des ensembles composés de drapeaux et que vous voulez seulement savoir s'il y a un chevauchement.

Dans ce cas, tout ce que vous devez vraiment savoir, c'est s'il y a une valeur non nulle [1] après avoir masqué le bit .

[1] Voir le commentaire de Jaime. Si nous faisions du masquage de bit authentiquement , nous n'aurions qu'à vérifier que le résultat était positif. Mais puisque enums peut être négatif, même étrangement, lorsqu'il est combiné avec l' [Flags] attribut , il est défensif de coder != 0plutôt que > 0.

Construire à partir de la configuration de @ andnil ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BitFlagPlay
{
    class Program
    {
        [Flags]
        public enum MyColor
        {
            Yellow = 0x01,
            Green = 0x02,
            Red = 0x04,
            Blue = 0x08
        }

        static void Main(string[] args)
        {
            var myColor = MyColor.Yellow | MyColor.Blue;
            var acceptableColors = MyColor.Yellow | MyColor.Red;

            Console.WriteLine((myColor & MyColor.Blue) != 0);     // True
            Console.WriteLine((myColor & MyColor.Red) != 0);      // False                
            Console.WriteLine((myColor & acceptableColors) != 0); // True
            // ... though only Yellow is shared.

            Console.WriteLine((myColor & MyColor.Green) != 0);    // Wait a minute... ;^D

            Console.Read();
        }
    }
}
ruffin
la source
L'énumération peut être basée sur un type signé, vous devez donc utiliser "! = 0" au lieu de "> 0".
corbeau
@JaimePardos - Tant que nous les gardons des octets honnêtes, comme je le fais dans cet exemple, il n'y a pas de concept de négatif. Juste de 0 à 255. Comme MSDN l'avertit , "Soyez prudent si vous définissez un nombre négatif comme une constante énumérée par indicateur, car ... [cela] pourrait rendre votre code déroutant et encourager les erreurs de codage." C'est étrange de penser en termes de "fanions négatifs"! ; ^) Je vais en éditer plus dans un peu. Mais vous avez raison, si nous utilisons des valeurs négatives dans notre enum, nous devons vérifier != 0.
ruffin
10

Les indicateurs vous permettent d'utiliser le masquage de bit à l'intérieur de votre énumération. Cela vous permet de combiner les valeurs d'énumération, tout en conservant celles qui sont spécifiées.

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}
Jay Mooney
la source
0

Toutes mes excuses si quelqu'un a déjà remarqué ce scénario. Un parfait exemple de drapeaux que nous pouvons voir en réflexion. Oui Binding Flags ENUM. https://docs.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=netframework-4.8

[System.Flags]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Serializable]
public enum BindingFlags

Usage

             // BindingFlags.InvokeMethod
            // Call a static method.
            Type t = typeof (TestClass);

            Console.WriteLine();
            Console.WriteLine("Invoking a static method.");
            Console.WriteLine("-------------------------");
            t.InvokeMember ("SayHello", BindingFlags.InvokeMethod | BindingFlags.Public | 
                BindingFlags.Static, null, null, new object [] {});
kbvishnu
la source