Por que as permissões enum geralmente têm 0, 1, 2, 4 valores?

159

Por que as pessoas sempre usam valores de enum como 0, 1, 2, 4, 8e não 0, 1, 2, 3, 4?

Isso tem algo a ver com operações de bits, etc.?

Eu realmente aprecio um pequeno trecho de amostra de como isso é usado corretamente :)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}
Pascal
fonte
1
possível duplicata de Enum como bandeira usando, configuração e mudança
Henk Holterman
25
Eu discordo do voto idiota.
precisa saber é o seguinte
A maneira UNIX de definir permissão também é baseada na mesma lógica.
22412 Rudy
3
@ Pascal: Você pode achar útil ler sobre o Bitwise OR (e o Bitwise AND ), que é o que |(e &) representam. As várias respostas pressupõem que você esteja familiarizado com isso.
22712 Brian
2
@IAdapter Entendo por que você pensaria isso, pois a resposta para ambos é a mesma, mas acho que as perguntas são diferentes. A outra pergunta apenas pede um exemplo ou explicação do atributo Flags em C #. Essa pergunta parece ser sobre o conceito de sinalizadores de bits e os fundamentos por trás deles.
Jeremy S

Respostas:

268

Porque eles são poderes de dois e eu posso fazer isso:

var permissions = Permissions.Read | Permissions.Write;

E talvez mais tarde ...

if( (permissions & Permissions.Write) == Permissions.Write )
{
    // we have write access
}

É um campo de bits, em que cada bit definido corresponde a alguma permissão (ou seja qual for o valor logicamente correspondente). Se eles fossem definidos, 1, 2, 3, ...você não seria capaz de usar operadores bit a bit dessa maneira e obter resultados significativos. Para aprofundar ...

Permissions.Read   == 1 == 00000001
Permissions.Write  == 2 == 00000010
Permissions.Delete == 4 == 00000100

Observe um padrão aqui? Agora, se pegarmos meu exemplo original, ou seja,

var permissions = Permissions.Read | Permissions.Write;

Então...

permissions == 00000011

Vejo? Os bits Reade Writeestão definidos, e posso verificar isso independentemente (observe também que o Deletebit não está definido e, portanto, esse valor não transmite permissão para excluir).

Ele permite armazenar vários sinalizadores em um único campo de bits.

Ed S.
fonte
2
@ Malcolm: Sim; myEnum.IsSet. Eu sou da opinião de que esta é uma abstração completamente inútil e serve apenas para reduzir a digitação, mas meh
Ed S.
1
Boa resposta, mas você deve mencionar por que o atributo Flags é aplicado e quando não deseja aplicar Flags em algumas enumerações.
Andy
3
@ Andy: Na verdade, o Flagsatributo faz pouco mais do que dar a você 'impressão bonita' iirc. Você pode usar um valor enumerado como um sinalizador, independentemente da presença do atributo.
22412 Ed S.
3
@detly: Porque as instruções if em C # requerem uma expressão booleana. 0não é false; falseé false. Você pode escrever if((permissions & Permissions.Write) > 0).
Ed S.
2
Em vez do 'complicado' (permissions & Permissions.Write) == Permissions.Write, agora você pode usarenum.HasFlag()
Louis Kottmann 23/10/12
147

Se ainda não estiver claro nas outras respostas, pense assim:

[Flags] 
public enum Permissions 
{   
   None = 0,   
   Read = 1,     
   Write = 2,   
   Delete = 4 
} 

é apenas uma maneira mais curta de escrever:

public enum Permissions 
{   
    DeleteNoWriteNoReadNo = 0,   // None
    DeleteNoWriteNoReadYes = 1,  // Read
    DeleteNoWriteYesReadNo = 2,  // Write
    DeleteNoWriteYesReadYes = 3, // Read + Write
    DeleteYesWriteNoReadNo = 4,   // Delete
    DeleteYesWriteNoReadYes = 5,  // Read + Delete
    DeleteYesWriteYesReadNo = 6,  // Write + Delete
    DeleteYesWriteYesReadYes = 7, // Read + Write + Delete
} 

Existem oito possibilidades, mas você pode representá-las como combinações de apenas quatro membros. Se houvesse dezesseis possibilidades, você poderia representá-las como combinações de apenas cinco membros. Se houvesse quatro bilhões de possibilidades, você poderia representá-las como combinações de apenas 33 membros! Obviamente, é muito melhor ter apenas 33 membros, cada um (exceto zero), um poder de dois, do que tentar nomear quatro bilhões de itens em uma enumeração.

Eric Lippert
fonte
32
+1 para a imagem mental de um enumcom quatro bilhões de membros. E a parte triste é que provavelmente alguém por aí já tentou.
precisa saber é o seguinte
23
@DanielPryden Como leitor diário do Daily WTF, eu acreditaria.
Maçante
1
2 ^ 33 = ~ 8,6 bilhões. Para 4 bilhões de valores diferentes, você precisa apenas de 32 bits.
um CVn
5
@ MichaelKjörling um dos 33 é para o 0 padrão
aberração catraca
@ MichaelKjörling: Para ser justo, existem apenas 32 membros com poderes de 2, já que 0 não é um poder de dois. Portanto, "33 membros, cada um com um poder de dois" não é exatamente correto (a menos que você conte 2 ** -infinitycomo um poder de dois).
22712 Brian
36

Como esses valores representam locais de bits exclusivos em binário:

1 == binary 00000001
2 == binary 00000010
4 == binary 00000100

etc., então

1 | 2 == binary 00000011

EDITAR:

3 == binary 00000011

3 em binário é representado por um valor de 1 nos dois lugares. Na verdade, é o mesmo que o valor 1 | 2. Portanto, quando você está tentando usar os locais binários como sinalizadores para representar algum estado, 3 geralmente não é significativo (a menos que exista um valor lógico que realmente seja a combinação dos dois)

Para esclarecimentos adicionais, convém estender seu exemplo de enum da seguinte maneira:

[Flags]
public Enum Permissions
{
  None = 0,   // Binary 0000000
  Read = 1,   // Binary 0000001
  Write = 2,  // Binary 0000010
  Delete = 4, // Binary 0000100
  All = 7,    // Binary 0000111
}

Portanto, em que eu tenho Permissions.All, eu também tenho implicitamente Permissions.Read, Permissions.WriteePermissions.Delete

Chris Shain
fonte
e qual é o problema com 2 | 3?
Pascal
1
@ Pascal: Por 3ser 11binário, ou seja, não é mapeado para um único conjunto de bits, então você perde a capacidade de mapear 1 bit em uma posição arbitrária para um valor significativo.
21412 Ed S.
8
@Pascal em outro sentido 2|3 == 1|3 == 1|2 == 3,. Então se você tem um valor com o binário 00000011, e suas bandeiras incluídos valores 1, 2e 3, em seguida, você não saberia se esse valor representa 1 and 3, 2 and 3, 1 and 2ou only 3. Isso torna muito menos útil.
precisa saber é
10
[Flags]
public Enum Permissions
{
    None   =    0; //0000000
    Read   =    1; //0000001
    Write  = 1<<1; //0000010
    Delete = 1<<2; //0000100
    Blah1  = 1<<3; //0001000
    Blah2  = 1<<4; //0010000
}

Acho que escrever assim é mais fácil de entender e ler, e você não precisa calculá-lo.

Dozer
fonte
5

Eles são usados ​​para representar sinalizadores de bits, o que permite combinações de valores de enumeração. Eu acho que é mais claro se você escrever os valores em notação hexadecimal

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}
JaredPar
fonte
4
@ Pascal: Talvez seja mais legível para você neste momento, mas à medida que você ganha experiência, visualizar bytes em hexadecimal se torna uma segunda natureza. Dois dígitos em hexadecimal mapeiam para um byte mapeiam para 8 bits (bem ... um byte geralmente é de 8 bits de qualquer maneira ... nem sempre é verdade, mas, neste exemplo, é aceitável generalizar).
Ed S.
5
@ Pascal rápido, o que você ganha quando se multiplica 4194304por 2? Que tal 0x400000? É muito mais fácil reconhecer 0x800000como a resposta correta do que 8388608e também é menos propenso a erros de digitar o valor hexadecimal.
Phoog 21/03/12
6
É muito mais fácil dizer, de relance, se suas bandeiras estão definidas corretamente (ou seja, são potências de 2), se você usar hexadecimal. É 0x10000uma potência de dois? Sim, começa com 1, 2, 4 ou 8 e possui todos os 0s depois. Você não precisa traduzir mentalmente de 0x10 a 16 (embora isso provavelmente se torne uma segunda natureza eventualmente), pense nisso como "algum poder de 2".
Brian
1
Estou absolutamente com Jared sobre ser muito mais fácil notar em hexadecimal. você acabou de usar 1 2 4 8 e mudança
Bevacqua
1
Pessoalmente, prefiro apenas usar, por exemplo, P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. Não tenho certeza se esse tipo de dobragem constante funciona em C #, mas funciona bem em C / C ++ (assim como em Java, eu acho).
macia
1

Isso é realmente mais um comentário, mas como isso não suporta a formatação, eu só queria incluir um método que empreguei para configurar as enumerações de sinalizadores:

[Flags]
public enum FlagTest
{
    None = 0,
    Read = 1,
    Write = Read * 2,
    Delete = Write * 2,
    ReadWrite = Read|Write
}

Considero essa abordagem especialmente útil durante o desenvolvimento no caso em que você deseja manter suas bandeiras em ordem alfabética. Se você determinar que precisa adicionar um novo valor de sinalizador, basta inseri-lo em ordem alfabética e o único valor a ser alterado é o que precede agora.

Observe, no entanto, que depois que uma solução é publicada em qualquer sistema de produção (especialmente se o enum é exposto sem um acoplamento rígido, como em um serviço da Web), é altamente recomendável não alterar qualquer valor existente no enum.

Mike Guthrie
fonte
1

Muitas respostas boas para essa aqui ... direi ... se você não gosta, ou não consegue entender facilmente o que a <<sintaxe está tentando expressar ... eu pessoalmente prefiro uma alternativa (e ouso dizer, estilo simples de declaração enum) ...

typedef NS_OPTIONS(NSUInteger, Align) {
    AlignLeft         = 00000001,
    AlignRight        = 00000010,
    AlignTop          = 00000100,
    AlignBottom       = 00001000,
    AlignTopLeft      = 00000101,
    AlignTopRight     = 00000110,
    AlignBottomLeft   = 00001001,
    AlignBottomRight  = 00001010
};

NSLog(@"%ld == %ld", AlignLeft | AlignBottom, AlignBottomLeft);

LOG 513 == 513

Muito mais fácil (pelo menos para mim) de compreender. Alinhe os… descreva o resultado desejado, obtenha o resultado que você deseja. Não são necessários "cálculos".

Alex Gray
fonte