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


De temps à autre, 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 [Flags]-attribute fait.

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


1123
2017-08-12 04:09


origine


Réponses:


L'attribut flags doit être utilisé chaque fois que l'énumérable représente une collection de drapeaux, plutôt qu'une seule valeur. De telles collections sont généralement manipulées à l'aide d'opérateurs au niveau du bit, par exemple:

myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Notez que [Flags] en soi ne change pas cela du tout - Tout ce qu'il fait est de permettre une belle représentation par le .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 [Flags]  ne fait pas automatiquement les puissances des valeurs enum de 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,
    Green,
    Red,
    Blue
}

Les valeurs, si elles sont déclarées de cette façon, seront Jaune = 0, Vert = 1, Rouge = 2, Bleu = 3. Ceci le rendra inutile pour être utilisé comme drapeau.

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 dans votre propriété, on peut le faire:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow has been set...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green has been set...
}    

ou, dans .NET 4 et plus tard:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow has been set...
}

Sous les couvertures

Cela fonctionne parce que vous avez déjà utilisé les pouvoirs de deux dans votre énumération. Sous les couvertures, vos valeurs d'énumération ressemblent à ceci (présenté comme bytes, qui a 8 bits qui peuvent être 1 ou 0)

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

De même, après avoir défini votre propriété AllowedColors en Rouge, Vert et Bleu (valeurs où OR'ed par le tuyau |), AllowedColors ressemble à ça

myProperties.AllowedColors: 00001110

Donc, lorsque vous récupérez la valeur, vous êtes en réalité au format AND et 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 0 dans votre énumération, en citant msdn:

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

Utilisez None comme nom de l'indicateur énuméré constant dont la valeur est zéro. Vous ne pouvez pas utiliser la constante non énumérée 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 binaire entre la valeur numérique et la constante non énumérée pour déterminer si les 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 conception de drapeaux à msdn


1752
2017-08-12 05:10



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 bit-shift 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


670
2017-08-12 04:37



Combiner des réponses https://stackoverflow.com/a/8462/1037948 (déclaration via bit-shifting) et https://stackoverflow.com/a/9117/1037948 (en utilisant des combinaisons dans la déclaration) vous pouvez décaler les valeurs précédentes plutôt que d'utiliser des nombres. 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,
}

Confirmer 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

86
2018-05-16 15:03



S'il vous plaît voir 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");
            }
        }
    }
}

44
2017-08-12 04:32



je demandé récemment à propos de quelque chose de similaire.

Si vous utilisez des indicateurs, vous pouvez ajouter une méthode d'extension à enums pour faciliter la vérification des indicateurs contenus (voir la publication 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,
    ...
}

Alors 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 moyens de vérifier les drapeaux inclus.


30
2017-08-12 18:40



@Nidonocu

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

Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
  && ((Mode & Mode.Read) == Mode.Read)));

19
2017-08-12 15:37



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 fonctionnent les drapeaux sous les couvertures.


16
2017-07-20 11:09



Ajouter Mode.Write:

Mode = Mode | Mode.Write;

15
2017-08-12 05:59



Il y a quelque chose de trop verbeux à propos de la if ((x & y) == y)... construire, surtout si x ET y sont les deux ensembles composés de drapeaux et vous voulez seulement savoir s'il y a tout chevauchement.

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

[1] Voir le commentaire de Jaime. Si nous étions authentiquement bitmasking, mer   seulement besoin de vérifier que le résultat était positif. Mais depuis enums   peut être négatif, même, étrangement, lorsqu'il est combiné avec la [Flags]   attribut,   il est défensif de coder pour != 0 plutôt que > 0.

Construire hors 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();
        }
    }
}

13
2018-05-01 23:57



Les drapeaux vous permettent d'utiliser le masquage de bit à l'intérieur de votre énumération. Cela vous permet de combiner des 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
}

10
2017-08-12 04:24