¿Qué significa el atributo [Flags] Enum en C#?


De vez en cuando veo una enumeración como la siguiente:

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

No entiendo qué hace exactamente el atributo [Flags] -.

Alguien tiene una buena explicación o ejemplo, se puede publicar?

 1158
Author: Vogel612, 2008-08-12

10 answers

El atributo flags debe usarse siempre que el enumerable represente una colección de flags, en lugar de un solo valor. Tales colecciones son generalmente manipuladas usando operadores bitwise, por ejemplo:

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

Tenga en cuenta que [Flags] por sí mismo no cambia esto en absoluto - todo lo que hace es habilitar una representación agradable por el método .ToString():

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"

También es importante señalar que [Flags] no convierte automáticamente los valores de enumeración en potencias de dos. Si si omite los valores numéricos, la enumeración no funcionará como cabría esperar en las operaciones de bits, porque por defecto los valores comienzan con 0 e incremento.

Declaración incorrecta:

[Flags]
public enum MyColors
{
    Yellow,
    Green,
    Red,
    Blue
}

Los valores, si se declaran de esta manera, serán Amarillo = 0, Verde = 1, Rojo = 2, Azul = 3. Esto hará que sea inútil para su uso como banderas.

Aquí hay un ejemplo de una declaración correcta:

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

Para recuperar los distintos valores en su propiedad, uno puede hacer esto:

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

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

O, en. NET 4 y posteriores:

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

Debajo de las cubiertas

Esto funciona porque previamente usaste poderes de dos en tu enumeración. Bajo las portadas, sus valores de enumeración se ven así (presentados como bytes, que tiene 8 bits que pueden ser 1 o 0)

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

Del mismo modo, después de haber establecido su propiedad AllowedColors a Rojo, Verde y Azul (que valora donde Or'ed por la tubería|), AllowedColors se ve como esto

myProperties.AllowedColors: 00001110

Así que cuando recuperas el valor en realidad estás en el sentido de bits AND'ing los valores

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

El valor None = 0

Y con respecto al uso 0 en su enumeración, citando de msdn:

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

Use None como el nombre de la constante enumerada de flag cuyo valor es cero. No puede usar la constante None enumerated en una operación bitwise AND para probar un flag porque el resultado es siempre cero. Sin embargo, puede realizar una lógica, no en bits, comparación entre el valor numérico y la constante None enumerated para determinar si se establecen bits en el valor numérico.

Puede encontrar más información sobre el atributo flags y su uso en msdn y designing flags en msdn

 1795
Author: andnil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-28 00:12:02

También puedes hacer esto

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

Encuentro el desplazamiento de bits más fácil que escribir 4,8,16,32 y así sucesivamente. No tiene ningún impacto en su código porque todo se hace en tiempo de compilación

 684
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-18 02:45:27

Combinando respuestas https://stackoverflow.com/a/8462/1037948 (declaración mediante desplazamiento de bits) y https://stackoverflow.com/a/9117/1037948 (usando combinaciones en la declaración) puede cambiar los valores anteriores en lugar de usar números. No necesariamente recomendarlo, pero simplemente señalar que puede.

En lugar de:

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

Puedes declarar

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

Confirmando con LinqPad:

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

Resultados en:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8
 87
Author: drzaus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:37

Por favor, vea el siguiente ejemplo que muestra la declaración y el uso potencial:

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
Author: OJ.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-14 02:23:29

Yo pregunté recientemente acerca de algo similar.

Si usa banderas, puede agregar un método de extensión a las enumeraciones para facilitar la comprobación de las banderas contenidas (consulte la publicación para obtener más detalles)

Esto le permite hacer:

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

Entonces puedes hacer:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

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

Encuentro esto más fácil de leer que la mayoría de las formas de comprobar las banderas incluidas.

 30
Author: Keith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:54

@Nidonocu

Para añadir otro indicador a un conjunto de valores existente, utilice el operador de asignación OR.

Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
  && ((Mode & Mode.Read) == Mode.Read)));
 19
Author: steve_c,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-12 15:37:42

En extensión a la respuesta aceptada, en C # 7 las banderas de enumeración se pueden escribir usando literales binarios:

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

Creo que esta representación deja claro cómo funcionan las banderas debajo de las cubiertas.

 17
Author: Thorkil Holm-Jacobsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-20 11:09:19

Para añadir Mode.Write:

Mode = Mode | Mode.Write;
 16
Author: David Wengier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-14 05:46:00

Hay algo demasiado detallado para mí acerca de la construcción if ((x & y) == y)..., especialmente si x Y y son conjuntos compuestos de banderas y solo quieres saber si hay cualquier superposición.

En este caso, todo lo que realmente necesita saber es si hay un valor distinto de cero[1] después de haber enmascarado.

[1] Ver el comentario de Jaime. Si fuéramos auténticamente máscara de bits, solo hay que comprobar que el resultado fue positivo. Pero desde enum s puede ser negativo, incluso, extrañamente, cuando se combina con el [Flags] atributo, es defensivo codificar para != 0 en lugar de > 0.

Construyendo a partir de la configuración 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
Author: ruffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-16 13:40:31

Las banderas le permiten usar máscara de bits dentro de su enumeración. Esto le permite combinar valores de enumeración, mientras conserva cuáles se especifican.

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}
 10
Author: Jay Mooney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-07 18:21:45