O que o Atributo Enum [Flags] significa em C #?

1447

De tempos em tempos, vejo um enum como o seguinte:

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

Não entendo exatamente o que o [Flags]atributo faz.

Alguém tem uma boa explicação ou exemplo que poderia postar?

Brian Leahy
fonte
Também vale a pena notar, além da resposta aceita, que o VB.NET realmente exige [Flags] - pelo menos de acordo com o pessoal do .NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/…
Rushyo 30/07/12
8
Observe que não é exigido no VB atualmente. Salvar comportamento como c # - apenas altera a saída ToString (). Observe que você também pode executar OR lógico, dentro do próprio Enum. Muito legal. Cat = 1, Dog = 2, CatAndDog = Cat || Cão.
Chalky
14
@ Chalky Você quer dizer CatAndDog = Cat | Dog(o OR lógico em vez do Condicional), presumo?
DdW 12/09
5
@DdW, parcialmente correto: | deve ser usado, mas | é chamado de OR binário. II é a lógica OR (que permite que um curto-circuito): Pelo menos de acordo com a Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx
Pieter21

Respostas:

2154

O [Flags]atributo deve ser usado sempre que o enumerável representar uma coleção de valores possíveis, em vez de um único valor. Essas coleções são frequentemente usadas com operadores bit a bit, por exemplo:

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

Observe que o [Flags]atributo não permite isso por si só - tudo o que faz é permitir uma boa representação pelo .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"

Também é importante notar que [Flags] não fazer automaticamente os valores ENUM potências de dois. Se você omitir os valores numéricos, a enumeração não funcionará como seria de esperar em operações bit a bit, porque, por padrão, os valores começam com 0 e incrementam.

Declaração incorreta:

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

Os valores, se declarados dessa maneira, serão Amarelo = 0, Verde = 1, Vermelho = 2, Azul = 3. Isso o tornará inútil como sinalizadores.

Aqui está um exemplo de uma declaração correta:

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

Para recuperar os valores distintos em sua propriedade, é possível fazer o seguinte:

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

ou anterior ao .NET 4:

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

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

Debaixo das cobertas

Isso funciona porque você usou poderes de dois em sua enumeração. Nos bastidores, seus valores de enumeração são assim em zeros e binários:

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

Da mesma forma, depois de definir sua propriedade AllowedColors como Vermelho, Verde e Azul usando o |operador binário OR bit a bit , AllowedColors fica assim:

myProperties.AllowedColors: 00001110

Portanto, quando você recupera o valor, está realmente executando AND bit a bit &nos valores:

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

O valor None = 0

E sobre o uso de 0na sua enumeração, citando o MSDN:

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

Use None como o nome da constante enumerada do sinalizador cujo valor é zero. Você não pode usar a constante enumerada None em uma operação AND bit a bit para testar um sinalizador porque o resultado é sempre zero. No entanto, você pode executar uma comparação lógica, não um bit a bit, entre o valor numérico e a constante enumerada None para determinar se algum bit no valor numérico está definido.

Você pode encontrar mais informações sobre o atributo flags e seu uso no msdn e sobre o design de flags no msdn

andnil
fonte
156
Bandeiras em si não faz nada. Além disso, o C # não requer sinalizadores por si só. Mas a ToStringimplementação de seu enum usa Flags, e assim faz Enum.IsDefined, Enum.Parseetc. Tente remover Bandeiras e olhar para o resultado de MyColor.Yellow | MyColor.Red; sem ele você obtém "5", com Flags você obtém "Amarelo, Vermelho". Algumas outras partes da estrutura também usam [Flags] (por exemplo, serialização XML).
Ruben
7
// Amarelo foi colocado ..., acho um pouco enganador. Nada foi definido, significa que o amarelo é um membro da sua AllowedColors; talvez seja melhor // o amarelo é permitido?
Scott Weaver
48
Prefiro usar constantes do formato A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Muito mais fácil de ler, entender, verificar visualmente e alterar.
Nick Westgate
2
@orrrden, sim yeahh! Encontrei o seguinte: msdn.microsoft.com/en-us/library/system.enum.aspx - consulte a parte "Observações": "Enum é a classe base para todas as enumerações no .NET Framework". e "A enumeração não herda explicitamente de Enum; o relacionamento de herança é tratado implicitamente pelo compilador". Então, quando você escreve: public enum bla bla bla - esse é um tipo de valor. Mas, o método HasFlag quer que você dê-lhe um exemplo de System.Enum que é uma classe (tipo de referência :)
Aleksei Chepovoi
2
Se você deseja excluir um sinalizador da enumeração, use xor, que é ^ em C #. Então, se você tiver myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Você pode fazer:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe
779

Você também pode fazer isso

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

Acho a mudança de bits mais fácil do que digitar 4,8,16,32 e assim por diante. Não tem impacto no seu código, porque tudo é feito em tempo de compilação

Orion Edwards
fonte
18
É isso que eu quero dizer, eu prefiro ter o int completo no código fonte. Se eu tiver uma coluna em uma tabela no banco de dados chamada MyEnum que armazene um valor de uma das enumerações e um registro tenha 131.072, precisaria usar minha calculadora para descobrir que isso corresponde à enumeração com o valor 1 << 17. Em vez de apenas ver o valor 131.072 escrito na fonte.
JeremyWeir
6
@jwg Eu concordo, é bobagem ficar muito preocupado com o desempenho em tempo de execução disso, mas, mesmo assim, acho bom saber que isso não será inserir turnos de bits em qualquer lugar em que você usar o enum. Mais de um 'isso é puro' coisa em vez de qualquer coisa relacionada ao desempenho
Orion Edwards
18
@ JeremyWeir - Vários bits serão definidos em um valor de enumeração de sinalizador. Portanto, seu método de análise de dados é inadequado. Execute um procedimento para representar seu valor inteiro em binário. 131.072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. Com o conjunto de 17 bits, um valor de enum atribuído 1 << 17 é facilmente determinável. 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. os valores de enumeração atribuem 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. etc. etc. ajuda de uma calculadora .. que duvido que você seria capaz de determinar sem obter o representante binário.
Brett Caswell
13
Também apontando pode bit-shift de valores enum "anteriores" ao invés de diretamente de números, ou seja Third = Second << 1, em vez de Third = 1 << 2- ver descrição mais completa abaixo
drzaus
26
I como esta, mas uma vez que você chegar aos limites superiores do tipo enum subjacente, o compilador não alertar contra mudanças bits, como 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2e assim por diante. Por outro lado, se você diz ThirtySecond = 2147483648para um tipo int enum, o compilador gera um erro.
aaaantoine
115

Combinando as respostas https://stackoverflow.com/a/8462/1037948 (declaração via deslocamento de bits) e https://stackoverflow.com/a/9117/1037948 (usando combinações na declaração), você pode alterar os valores anteriores em vez de do que usar números. Não necessariamente recomendando, mas apenas apontando que você pode.

Ao invés 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,
}

Você pode 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 com LinqPad:

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

Resulta em:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8
drzaus
fonte
23
As combinações são uma boa recomendação, mas acho que a troca de bits em cadeia seria propensa a erros de copiar e colar, como Dois = Um << 1, Três = Um << 1, etc ... Os números inteiros incrementais do o formulário 1 << n é mais seguro e a intenção é mais clara.
Rupert Rawnsley
2
@RupertRawnsley para citar a minha resposta:> Não necessariamente recomendando, mas apenas apontando que você pode
drzaus
49

Consulte o seguinte para um exemplo que mostra a declaração e o 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");
            }
        }
    }
}
JO.
fonte
Este exemplo funciona mesmo se você deixar de fora [Flags]. É sobre a parte [Flags] que estou tentando aprender.
gbarry
39

Em extensão à resposta aceita, em C # 7 os sinalizadores de enum podem ser escritos usando literais binários:

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

Eu acho que essa representação deixa claro como as bandeiras funcionam embaixo das cobertas .

Thorkil Holm-Jacobsen
fonte
2
E no C # 7.2 é ainda mais claro com o separador à esquerda ! 0b_0100
Vincenzo Petronio
37

Eu perguntei recentemente sobre algo semelhante.

Se você usar sinalizadores, poderá adicionar um método de extensão às enumerações para facilitar a verificação dos sinalizadores contidos (consulte a publicação para obter detalhes)

Isso permite que você faça:

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

Então você pode fazer:

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

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

Acho isso mais fácil de ler do que a maioria das maneiras de verificar os sinalizadores incluídos.

Keith
fonte
IsSet é um método de extensão que eu assumo?
Robert MacLean
Sim - leia a outra pergunta que eu conectar-se a detalhes: stackoverflow.com/questions/7244
Keith
71
NET 4 adiciona um HasFlagmétodo para enumerações, de modo que você pode fazer opt.HasFlag( PossibleOptions.OptionOne )sem ter que escrever suas próprias extensões
Orion Edwards
1
Observe que HasFlagé muito mais lento que fazer operações bit a bit.
Wai Ha Lee
1
@WaiHaLee Você pode usar o método de extensão CodeJam.EnumHelper.IsFlagSet que é compilado para uma versão rápida usando Expression: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_
22

@Nidonocu

Para adicionar outro sinalizador a um conjunto de valores existente, use o operador de atribuição OR.

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

Ao trabalhar com sinalizadores, muitas vezes declaro itens adicionais Nenhum e Todos. Isso é útil para verificar se todos os sinalizadores estão definidos ou se nenhum sinalizador está definido.

[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

 
Atualização 2019-10:

Desde o C # 7.0, você pode usar literais binários, provavelmente mais intuitivos de ler:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

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

    All =      0b1111

}
Jpsy
fonte
17

Para adicionar Mode.Write:

Mode = Mode | Mode.Write;
David Wengier
fonte
57
ou Mode | = Mode.Write
abatishchev
14

Há algo excessivamente detalhado para mim sobre a if ((x & y) == y)...construção, especialmente se xAND ysão conjuntos compostos de sinalizadores e você só quer saber se há alguma sobreposição.

Nesse caso, tudo o que você realmente precisa saber é se existe um valor diferente de zero [1] após a mascaragem .

[1] Veja o comentário de Jaime. Se estivéssemos autenticamente usando máscaras de bits , precisaríamos apenas verificar se o resultado era positivo. Mas como enums pode ser negativo, mesmo que estranhamente, quando combinado com o [Flags] atributo , é defensivo codificar em != 0vez de > 0.

Criando fora da configuração do @ 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
fonte
A enum pode ser baseada em um tipo assinado, portanto, você deve usar "! = 0" em vez de "> 0".
Raven #
@ JaimePardos - Desde que os mantenhamos como bytes honestos, como faço neste exemplo, não há conceito de negativo. Apenas 0 a 255. Como o MSDN alerta : "Tenha cuidado se você definir um número negativo como uma constante enumerada por sinalizador, porque ... [isso] pode tornar seu código confuso e incentivar erros de codificação". É estranho pensar em termos de "bitflags negativos"! ; ^) Vou editar mais daqui a pouco. Mas você está certo, se usarmos valores negativos em nosso enum, precisaremos verificar != 0.
Ruffin
10

Os sinalizadores permitem que você use máscaras de bits na sua enumeração. Isso permite combinar valores de enumeração, mantendo quais são especificados.

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

Desculpas se alguém já percebeu esse cenário. Um exemplo perfeito de bandeiras que podemos ver na reflexão. Sim Sinalizadores de ligação 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
fonte