¿Qué significa el atributo de enumeración [Flags] 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é [Flags]
hace exactamente el atributo.
¿Alguien tiene una buena explicación o ejemplo que pueda publicar?
El [Flags]
atributo debe usarse siempre que el enumerable represente una colección de valores posibles, en lugar de un valor único. Estas colecciones se utilizan a menudo 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í solo; todo lo que hace es permitir una buena representación mediante 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 convierte automáticamente los valores de enumeración en potencias de dos. Si omite los valores numéricos, la enumeración no funcionará como cabría esperar en operaciones bit a bit, porque 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 los hará inútiles como banderas.
A continuación se muestra 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, se 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 potencias de dos en tu enumeración. Debajo de las sábanas, sus valores de enumeración se ven así en unos y ceros binarios:
Yellow: 00000001
Green: 00000010
Red: 00000100
Blue: 00001000
De manera similar, después de haber configurado su propiedad AllowedColors en Rojo, Verde y Azul usando el |
operador binario bit a bit OR, AllowedColors se ve así:
myProperties.AllowedColors: 00001110
Entonces, cuando recuperas el valor, en realidad estás 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 0
en su enumeración, citando de MSDN:
[Flags]
public enum MyColors
{
None = 0,
....
}
Utilice Ninguno como nombre de la constante enumerada del indicador cuyo valor es cero. No puede utilizar la constante enumerada Ninguno en una operación AND bit a 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 ha establecido algún bit en el valor numérico.
Puede encontrar más información sobre el atributo flags y su uso en msdn y sobre el diseño de flags en msdn.
También puedes hacer esto
[Flags]
public enum MyEnum
{
None = 0,
First = 1 << 0,
Second = 1 << 1,
Third = 1 << 2,
Fourth = 1 << 3
}
Encuentro que el cambio de bits es más fácil que escribir 4,8,16,32, etc. No tiene ningún impacto en su código porque todo se hace en tiempo de compilación.
Combinando respuestas https://stackoverflow.com/a/8462/1037948 (declaración mediante cambio de bits) y https://stackoverflow.com/a/9117/1037948 (usando combinaciones en la declaración) puede cambiar los valores anteriores en lugar de que usar números. No necesariamente recomendarlo, pero simplemente señalar que puedes hacerlo.
En vez 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
Además de la respuesta aceptada, en C#7 los indicadores 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 bajo las sábanas .