Che cosa significa l'attributo Enum [Flags] in C #?


1447

Di tanto in tanto vedo un enum come il seguente:

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

Non capisco esattamente cosa fa l' [Flags]attributo.

Qualcuno ha una buona spiegazione o esempio che potrebbe pubblicare?


Vale anche la pena notare, oltre alla risposta accettata, che VB.NET richiede effettivamente [Flags] - almeno secondo i ragazzi di .NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/…
Rushyo,

8
Nota, non richiesto in VB in questi giorni. Salva comportamento come C #: modifica semplicemente l'output ToString (). Nota, puoi anche fare OR logico, ENTRO lo stesso Enum. Molto bello. Cat = 1, Dog = 2, CatAndDog = Cat || Cane.
Chalky

14
@Chalky Intendi CatAndDog = Cat | Dog(l'OR logico invece del Condizionale), suppongo?
DdW,

5
@DdW, parzialmente corretto: | dovrebbe essere usato, ma | è chiamato OR binario. II è l'OR logico (che permette corto circuito): Almeno secondo Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx
Pieter21

Risposte:


2154

L' [Flags]attributo dovrebbe essere usato ogni volta che l'enumerabile rappresenta una raccolta di valori possibili, anziché un singolo valore. Tali raccolte vengono spesso utilizzate con operatori bit a bit, ad esempio:

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

Nota che l' [Flags]attributo non lo abilita da solo - tutto ciò che fa è consentire una buona rappresentazione con il .ToString()metodo:

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"

È anche importante notare che [Flags] non rende automaticamente i valori di enum potenze di due. Se si omettono i valori numerici, l'enum non funzionerà come ci si aspetterebbe in operazioni bit a bit, perché per impostazione predefinita i valori iniziano con 0 e incrementano.

Dichiarazione errata:

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

I valori, se dichiarati in questo modo, saranno Giallo = 0, Verde = 1, Rosso = 2, Blu = 3. Questo lo renderà inutile come flag.

Ecco un esempio di una dichiarazione corretta:

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

Per recuperare i valori distinti nella tua proprietà, puoi farlo:

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

o prima di .NET 4:

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

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

Sotto le coperte

Questo funziona perché hai usato poteri di due nella tua enumerazione. Sotto le copertine, i valori di enumerazione si presentano così in binari e zeri:

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

Allo stesso modo, dopo aver impostato la proprietà AllowColors su Red, Green e Blue utilizzando l' |operatore binario OR bit a bit , AllowColors è simile al seguente:

myProperties.AllowedColors: 00001110

Quindi, quando recuperi il valore, stai effettivamente eseguendo AND bit a bit &sui valori:

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

Il valore Nessuno = 0

E per quanto riguarda l'uso di 0nella tua enumerazione, citando da MSDN:

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

Utilizzare None come nome della costante elencata flag il cui valore è zero. Non è possibile utilizzare la costante enumerata None in un'operazione AND bit a bit per verificare un flag perché il risultato è sempre zero. Tuttavia, è possibile eseguire un confronto logico, non a livello di bit, tra il valore numerico e la costante enumerata Nessuno per determinare se sono impostati eventuali bit nel valore numerico.

Puoi trovare maggiori informazioni sull'attributo flags e sul suo utilizzo su msdn e progettare flag su msdn


156
Le bandiere stesse non fanno nulla. Inoltre, C # non richiede Flags di per sé. Ma la ToStringrealizzazione del vostro enum utilizza etichette, e così fa Enum.IsDefined, Enum.Parseecc Provare a rimuovere Bandiere e esaminare il risultato di MyColor.Yellow | MyColor.Red; senza di essa ottieni "5", con le bandiere ottieni "Giallo, Rosso". Alcune altre parti del framework utilizzano anche [Flags] (ad esempio, serializzazione XML).
Ruben,

7
// Il giallo è stato impostato ... Trovo un po 'fuorviante. Non è stato impostato nulla, significa che Yellow è un membro dei tuoi AllowColors, forse meglio sarebbe // Yellow è consentito?
Scott Weaver,

48
Preferisco usare costanti del modulo A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Molto più facile da leggere, comprendere, controllare e modificare visivamente.
Nick Westgate,

2
@borrrden, diavolo sì! Ho trovato questo: msdn.microsoft.com/en-us/library/system.enum.aspx - vedere la parte "Note": "Enum è la classe di base per tutte le enumerazioni in .NET Framework". e "L'enumerazione non eredita esplicitamente da Enum; la relazione di ereditarietà è gestita implicitamente dal compilatore." Quindi, quando scrivi: public enum bla bla bla - questo è un tipo di valore. Ma il metodo HasFlag vuole che tu gli dia un'istanza di System.Enum che è una classe (tipo di riferimento :)
Aleksei Chepovoi,

2
Se si desidera escludere un flag dall'enumerazione, utilizzare xor, che è ^ in C #. Quindi se hai myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Puoi fare:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe,

780

Puoi anche farlo

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

Trovo il bit-shifting più facile che scrivere 4,8,16,32 e così via. Non ha alcun impatto sul codice perché è tutto eseguito in fase di compilazione


18
Questo è ciò che intendo, preferisco avere l'intero int nel codice sorgente. Se ho una colonna su una tabella nel database denominata MyEnum che memorizza il valore di uno degli enum e un record ha 131.072, avrei bisogno di uscire dalla mia calcolatrice per capire che ciò corrisponde all'enum con il valore 1 << 17. Al contrario di vedere solo il valore 131.072 scritto nella fonte.
JeremyWeir,

6
@jwg Sono d'accordo, è sciocco essere eccessivamente preoccupati per le prestazioni di runtime di questo, ma comunque, penso che sia bello sapere che questo non inserirà bitshift ovunque tu usi l'enum. Più di una cosa "ordinata" piuttosto che qualsiasi cosa legata alla performance
Orion Edwards,

18
@JeremyWeir - Verranno impostati diversi bit in un valore di enumerazione di flag. Quindi il tuo metodo di analisi dei dati è ciò che è improprio. Eseguire una procedura per rappresentare il valore intero in binario. 131.072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. Con il 17 ° bit impostato, un valore enum assegnato 1 << 17 è facilmente determinabile. 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. i valori enum assegnano 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. ecc. Ecc. Sono determinabili senza nemmeno il aiuto di una calcolatrice a tutti .. che dubito che saresti in grado di determinare affatto senza ottenere il rappresentante binario.
Brett Caswell

13
Inoltre, puoi spostare bit dai valori enum "precedenti" piuttosto che direttamente dai numeri, ovvero Third = Second << 1piuttosto che Third = 1 << 2- vedi la descrizione più completa di seguito
drzaus,

26
Mi piace questo, ma una volta che si arriva ai limiti superiori di tipo enum sottostante, il compilatore non mettere in guardia contro i turni bit come 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2e così via. Al contrario, se dici ThirtySecond = 2147483648per un enum di tipo int, il compilatore genera un errore.
aaaantoine,

115

Combinando le risposte https://stackoverflow.com/a/8462/1037948 (dichiarazione tramite bit-shifting) e https://stackoverflow.com/a/9117/1037948 (usando le combinazioni nella dichiarazione) è possibile spostare in bit i valori precedenti piuttosto che usare i numeri. Non necessariamente raccomandandolo, ma solo sottolineando che puoi.

Piuttosto che:

[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,
}

Puoi dichiarare

[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,
}

Conferma con LinqPad:

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

Risultati in:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8

23
Le combinazioni sono una buona raccomandazione, ma penso che il bit-shift incatenato sarebbe soggetto a errori di copia e incolla come Due = Uno << 1, Tre = Uno << 1, ecc ... Gli interi incrementali del modulo 1 << n sono più sicuri e l'intento è più chiaro.
Rupert Rawnsley,

2
@RupertRawnsley per citare la mia risposta:> Non necessariamente raccomandandolo, ma solo sottolineando che puoi
drzaus

49

Per un esempio, vedere quanto segue che mostra la dichiarazione e il potenziale utilizzo:

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

Questo esempio funziona anche se si lascia fuori [Flag]. È la parte [Flags] che sto cercando di imparare.
Gbarry,

39

In aggiunta alla risposta accettata, in C # 7 i flag enum possono essere scritti usando valori letterali binari:

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

Penso che questa rappresentazione chiarisca come funzionano le bandiere sotto le coperte .


2
E in C # 7.2 è ancora più chiaro con il separatore principale ! 0b_0100
Vincenzo Petronio,

37

Ho chiesto di recente qualcosa di simile.

Se usi i flag puoi aggiungere un metodo di estensione agli enum per rendere più facile il controllo dei flag contenuti (vedi post per i dettagli)

Questo ti permette di fare:

[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,
    ...
}

Quindi puoi fare:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

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

Trovo questo più facile da leggere rispetto alla maggior parte dei modi di controllare i flag inclusi.


IsSet è un metodo di estensione presumo?
Robert MacLean,

Sì, leggi l'altra domanda a cui rimando per i dettagli: stackoverflow.com/questions/7244
Keith

71
.NET 4 aggiunge un HasFlagmetodo alle enumerazioni, quindi puoi fare a opt.HasFlag( PossibleOptions.OptionOne )meno di dover scrivere le tue estensioni
Orion Edwards,

1
Si noti che HasFlagè molto più lento rispetto alle operazioni bit per bit.
Wai Ha Lee,

1
@WaiHaLee Puoi usare il metodo di estensione CodeJam.EnumHelper.IsFlagSet che viene compilato in una versione veloce usando Expression: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_

22

@Nidonocu

Per aggiungere un altro flag a un set di valori esistente, utilizzare l'operatore di assegnazione OR.

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

18

Quando lavoro con le bandiere, dichiaro spesso nessuno e tutti gli articoli. Questi sono utili per verificare se tutti i flag sono impostati o se non è stato impostato alcun flag.

[Flags] 
enum SuitsFlags { 

    None =     0,

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

    All =      ~(~0 << 4)

}

Uso:

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

 
Aggiornamento 2019-10:

Da C # 7.0 puoi usare letterali binari, che sono probabilmente più intuitivi da leggere:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

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

    All =      0b1111

}


14

C'è qualcosa di eccessivamente prolisso per me nel if ((x & y) == y)...costrutto, specialmente se xAND ysono entrambi insiemi composti di flag e vuoi solo sapere se c'è qualche sovrapposizione.

In questo caso, tutto ciò che devi veramente sapere è se c'è un valore diverso da [1] dopo aver mascherato il bit .

[1] Vedi il commento di Jaime. Se fossimo autenticamente bitmasking , avremmo solo bisogno di verificare che il risultato fosse positivo. Ma poiché enums può essere negativo, anche, stranamente, se combinato con l' [Flags] attributo , è difensivo codificare != 0piuttosto che > 0.

Creazione della configurazione di @ 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();
        }
    }
}

Enum può essere basato su un tipo firmato, quindi dovresti usare "! = 0" invece di "> 0".
corvo

@JaimePardos - Fintanto che li manteniamo byte onesti, come faccio in questo esempio, non esiste un concetto di negativo. Solo da 0 a 255. Come avverte MSDN , "Prestare attenzione se si definisce un numero negativo come costante elencata flag perché ... [che] potrebbe rendere il codice confuso e incoraggiare errori di codifica." È strano pensare in termini di "bitflags negative"! ; ^) Ne modificherò di più tra poco. Ma hai ragione, se usiamo valori negativi nel nostro enum, dovremmo verificare != 0.
ruffin,

10

I flag ti consentono di utilizzare il bitmasking all'interno della tua enumerazione. Ciò consente di combinare i valori di enumerazione, mantenendo quelli specificati.

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

0

Ci scusiamo se qualcuno ha già notato questo scenario. Un esempio perfetto di bandiere che possiamo vedere in riflessione. Sì 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

uso

             // 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 [] {});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.