¿Qué significa el atributo de enumeración [Flags] en C #?

1447

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 [Flags]atributo.

¿Alguien tiene una buena explicación o ejemplo que podrían publicar?

Brian Leahy
fuente
También vale la pena señalar, además de la respuesta aceptada, que VB.NET realmente requiere [Banderas], al menos según los chicos de .NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/…
Rushyo
8
Nota, no se requiere en VB en estos días. Guardar comportamiento como C #: solo cambia la salida de ToString (). Tenga en cuenta que también puede hacer OR lógico, DENTRO de la Enum. Muy genial. Cat = 1, Dog = 2, CatAndDog = Cat || Perro.
Chalky
14
@Chalky ¿Quieres decir CatAndDog = Cat | Dog(el OR lógico en lugar del Condicional), supongo?
DdW
55
@DdW, parcialmente correcto: | debe usarse, pero | se llama el binario OR. II es el OR lógico (que permite cortocircuitos): al menos según Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx
Pieter21

Respuestas:

2154

El [Flags]atributo debe usarse siempre que el enumerable represente una colección de valores posibles, en lugar de un solo valor. Tales colecciones a menudo se usan con operadores bit a bit, por ejemplo:

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

Tenga en cuenta que el [Flags]atributo no habilita esto por sí mismo; todo lo que hace es permitir una buena representación por el .ToString()método:

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 tener en cuenta que [Flags] no hace automáticamente que los valores de enumeración sean potencias de dos. Si omite los valores numéricos, la enumeración no funcionará como cabría esperar en las operaciones bit a bit, ya que de forma predeterminada los valores comienzan con 0 y se incrementan.

Declaración incorrecta

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

Los valores, si se declaran de esta manera, serán Amarillo = 0, Verde = 1, Rojo = 2, Azul = 3. Esto lo hará inútil 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 valores distintos en su propiedad, uno puede hacer esto:

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

o antes de .NET 4:

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

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

Debajo de las sábanas

Esto funciona porque usaste poderes de dos en tu enumeración. Debajo de las cubiertas, sus valores de enumeración se ven así en binarios y ceros:

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

Del mismo modo, después de haber configurado su propiedad AllowColors en Rojo, Verde y Azul utilizando el |operador OR binario a nivel de bit , AllowColors se ve así:

myProperties.AllowedColors: 00001110

Entonces, cuando recupera el valor, en realidad está realizando bit a bit Y &en los valores:

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

El valor Ninguno = 0

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

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

Use None como el nombre de la constante enumerada de bandera cuyo valor es cero. No puede usar la constante enumerada None en una operación AND a nivel de bit para probar un indicador porque el resultado siempre es cero. Sin embargo, puede realizar una comparación lógica, no bit a bit, entre el valor numérico y la constante Ninguno enumerado 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 diseñar banderas en msdn

andnil
fuente
156
Banderas en sí mismo no hace nada. Además, C # no requiere banderas en sí. Sin embargo, la ToStringimplementación de su enumeración utiliza Banderas, y lo mismo ocurre Enum.IsDefined, Enum.Parse, etc. Trate de eliminar Banderas y observe el resultado de MyColor.Yellow | MyColor.Red; sin él obtienes "5", con Banderas obtienes "Amarillo, Rojo". Algunas otras partes del marco también usan [Banderas] (por ejemplo, serialización XML).
Ruben
77
// El amarillo se ha configurado ..., me parece un poco engañoso. No se ha establecido nada, significa que Yellow es miembro de su AllowColors, quizás mejor sería // ¿Yellow está permitido?
Scott Weaver,
48
Prefiero usar constantes de la forma A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Mucho más fácil de leer, comprender, verificar visualmente y cambiar.
Nick Westgate
2
@borrrden, demonios, sí! Encontré esto: msdn.microsoft.com/en-us/library/system.enum.aspx - vea la parte "Comentarios": "Enum es la clase base para todas las enumeraciones en .NET Framework". y "La enumeración no hereda explícitamente de Enum; la relación de herencia es manejada implícitamente por el compilador". Entonces, cuando escribes: public enum bla bla bla - este es un tipo de valor. Pero, el método HasFlag quiere que le des una instancia de System.Enum que es una clase (tipo de referencia :)
Aleksei Chepovoi
2
Si desea excluir un indicador de la enumeración, use xor, que es ^ en C #. Entonces si tienes myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Puedes hacer:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe
780

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 cambio de bits más fácil que escribir 4,8,16,32 y así sucesivamente. No tiene impacto en su código porque todo se hace en tiempo de compilación

Orion Edwards
fuente
18
Eso es lo que quiero decir, prefiero tener el int completo en el código fuente. Si tengo una columna en una tabla en la base de datos llamada MyEnum que almacena un valor de una de las enumeraciones, y un registro tiene 131,072, necesitaría sacar mi calculadora para descubrir que corresponde a la enumeración con el valor 1 << 17. A diferencia de solo ver el valor 131,072 escrito en la fuente.
JeremyWeir
66
@jwg Estoy de acuerdo, es una tontería estar demasiado preocupado por el rendimiento en tiempo de ejecución de esto, pero de todos modos, creo que es bueno saber que esto no va a insertar cambios de bits en cualquier lugar donde use la enumeración. Más de una cosa 'ordenada' en lugar de algo relacionado con el rendimiento
Orion Edwards
18
@JeremyWeir: se establecerán varios bits en un valor de enumeración de marca. Entonces, su método de análisis de datos es lo que es incorrecto. Ejecute un procedimiento para representar su valor entero en binario. 131,072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. Con el bit 17 establecido, un valor de enumeración asignado 1 << 17 es fácilmente determinable. 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. los valores de enumeración asignan 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. etc. etc. es determinable sin siquiera ayuda de una calculadora ... lo cual dudo que pueda determinar sin obtener el representante binario.
Brett Caswell
13
También señalando que puede cambiar bit de los valores de enumeración "anteriores" en lugar de directamente de los números, es decir, en Third = Second << 1lugar de Third = 1 << 2- vea una descripción más completa a continuación
drzaus
26
Me gusta, pero una vez que llegue a los límites superiores del tipo de enumeración subyacente, el compilador no advierten contra desplazamientos de bit como 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2, y así sucesivamente. Por el contrario, si dice ThirtySecond = 2147483648para un tipo de enumeración int, el compilador arroja un error.
aaaantoine
115

Combinando las respuestas https://stackoverflow.com/a/8462/1037948 (declaración mediante bit-shifting) y https://stackoverflow.com/a/9117/1037948 (usando combinaciones en la declaración) puede cambiar bit en lugar de los valores anteriores que usar números. No necesariamente lo recomiendo, sino solo señalar que puedes.

Más bien 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,
}

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
drzaus
fuente
23
Las combinaciones son una buena recomendación, pero creo que el desplazamiento de bits encadenado sería propenso a copiar y pegar errores como Two = One << 1, Three = One << 1, etc. Los números enteros incrementales de forma 1 << n son más seguros y la intención es más clara.
Rupert Rawnsley
2
@RupertRawnsley para citar mi respuesta:> No necesariamente lo recomiendo, pero solo
señalo
49

Consulte lo siguiente para ver un 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");
            }
        }
    }
}
DO
fuente
Este ejemplo funciona incluso si deja de lado [Banderas]. Es la parte [Banderas] de la que estoy tratando de aprender.
gbarry
39

En extensión a la respuesta aceptada, en C # 7 los indicadores de enumeración se pueden escribir utilizando literales binarios:

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

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

Thorkil Holm-Jacobsen
fuente
2
¡Y en C # 7.2 es aún más claro con el separador principal ! 0b_0100
Vincenzo Petronio
37

Me preguntó recientemente sobre algo similar.

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

Esto te 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 verificar las banderas incluidas.

Keith
fuente
IsSet es un método de extensión, supongo?
Robert MacLean
Sí, lea la otra pregunta a la que enlace para obtener más detalles: stackoverflow.com/questions/7244
Keith
71
.NET 4 agrega un HasFlagmétodo a las enumeraciones, para que pueda hacerlo opt.HasFlag( PossibleOptions.OptionOne )sin tener que escribir sus propias extensiones
Orion Edwards
1
Tenga en cuenta que HasFlages mucho más lento que hacer operaciones bit a bit.
Wai Ha Lee
1
@WaiHaLee Puede usar el método de extensión CodeJam.EnumHelper.IsFlagSet que se compila en una versión rápida usando Expression: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_
22

@Nidonocu

Para agregar otro indicador a un conjunto de valores existente, use 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)));
steve_c
fuente
18

Cuando trabajo con banderas, a menudo declaro Ninguno y Todos los elementos adicionales. Estos son útiles para verificar si todas las banderas están configuradas o no.

[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

 
Actualización 2019-10:

Desde C # 7.0 puede usar literales binarios, que probablemente sean más intuitivos de leer:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

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

    All =      0b1111

}
Jpsy
fuente
17

Para agregar Mode.Write:

Mode = Mode | Mode.Write;
David Wengier
fuente
57
o Mode | = Mode.Write
abatishchev
14

Hay algo demasiado detallado para mí acerca de la if ((x & y) == y)...construcción, especialmente si xAND yson conjuntos compuestos de banderas y solo desea saber si hay alguna 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 estuviéramos auténticamente enmascarando bits , solo tendríamos que verificar que el resultado fuera positivo. Pero dado que enums puede ser negativo, incluso, extrañamente, cuando se combina con el [Flags] atributo , es defensivo codificar en != 0lugar de hacerlo > 0.

Partiendo 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();
        }
    }
}
ruffin
fuente
Enum puede basarse en un tipo con signo, por lo que debe usar "! = 0" en lugar de "> 0".
cuervo
@JaimePardos: siempre que mantengamos bytes honestos, como hago en este ejemplo, no hay un concepto negativo. Solo de 0 a 255. Como advierte MSDN , "Tenga cuidado si define un número negativo como un indicador enumerado constante porque ... [eso] podría hacer que su código sea confuso y alentar errores de codificación". ¡Es extraño pensar en términos de "banderas de bits negativas"! ; ^) Editaré más en un momento. Pero tiene razón, si usamos valores negativos en nuestro enum, tendríamos que verificarlo != 0.
ruffin
10

Las banderas le permiten usar bitmasking 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
}
Jay Mooney
fuente
0

Disculpas si alguien ya notó este escenario. Un ejemplo perfecto de banderas que podemos ver en la reflexión. Sí Banderas vinculantes 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 [] {});
kbvishnu
fuente