¿Por qué los permisos de enumeración a menudo tienen valores 0, 1, 2, 4?

159

¿Por qué las personas siempre usan valores de enumeración como 0, 1, 2, 4, 8y no 0, 1, 2, 3, 4?

¿Tiene esto algo que ver con operaciones de bits, etc.?

Realmente agradecería un pequeño fragmento de muestra sobre cómo se usa correctamente :)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}
Pascal
fuente
1
posible duplicado de Enum como bandera usando, configurando y cambiando
Henk Holterman
25
No estoy de acuerdo con el voto de engaño.
zzzzBov
La forma UNIX de establecer permisos también se basa en la misma lógica.
Rudy
3
@Pascal: Puede que le resulte útil leer sobre Bitwise OR (y Bitwise AND ), que es lo que representa |(y &). Las diversas respuestas suponen que está familiarizado con él.
Brian
2
@IAdapter Puedo ver por qué piensas eso, ya que las respuestas a ambas son las mismas, pero creo que las preguntas son diferentes. La otra pregunta solo pide un ejemplo o explicación del atributo Flags en C #. Esta pregunta parece ser sobre el concepto de banderas de bits y los fundamentos detrás de ellas.
Jeremy S

Respuestas:

268

Porque son poderes de dos y puedo hacer esto:

var permissions = Permissions.Read | Permissions.Write;

Y tal vez más tarde ...

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

Es un campo de bits, donde cada bit establecido corresponde a algún permiso (o lo que corresponda lógicamente al valor enumerado). Si estos se definieran como 1, 2, 3, ...no podría utilizar operadores bit a bit de esta manera y obtener resultados significativos. Para profundizar ...

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

¿Ves un patrón aquí? Ahora si tomamos mi ejemplo original, es decir,

var permissions = Permissions.Read | Permissions.Write;

Luego...

permissions == 00000011

¿Ver? Tanto los Ready Writese establecen los bits, y puedo comprobar que de forma independiente (Observe también que el Deletebit es no establece, por lo que este valor no transmiten permiso para eliminar).

Le permite a uno almacenar múltiples banderas en un solo campo de bits.

Ed S.
fuente
2
@ Malcolm: lo hace; myEnum.IsSet. Soy de la opinión de que esta es una abstracción completamente inútil y sirve solo para reducir la escritura, pero meh
Ed S.
1
Buena respuesta, pero debe mencionar por qué se aplica el atributo Flags y cuándo no desea aplicar Flags a algunas enumeraciones también.
Andy
3
@Andy: En realidad, el Flagsatributo hace poco más que darle iirc de 'impresión bonita'. Puede usar un valor enumerado como indicador independientemente de la presencia del atributo.
Ed S.
3
@detly: Porque si las declaraciones en C # requieren una expresión booleana. 0no es false; falsees false. Sin embargo, podrías escribir if((permissions & Permissions.Write) > 0).
Ed S.
2
En lugar del 'complicado' (permissions & Permissions.Write) == Permissions.Write, ahora puede usarenum.HasFlag()
Louis Kottmann el
147

Si aún no está claro en las otras respuestas, piénselo de esta manera:

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

es solo una forma más corta de escribir:

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
} 

Hay ocho posibilidades, pero puede representarlas como combinaciones de solo cuatro miembros. Si hubiera dieciséis posibilidades, podría representarlas como combinaciones de solo cinco miembros. Si hubiera cuatro mil millones de posibilidades, ¡podría representarlas como combinaciones de solo 33 miembros! Obviamente, es mucho mejor tener solo 33 miembros, cada uno (excepto cero) una potencia de dos, que tratar de nombrar cuatro mil millones de elementos en una enumeración.

Eric Lippert
fuente
32
+1 para la imagen mental de un enumcon cuatro mil millones de miembros. Y la parte triste es que probablemente alguien lo haya intentado.
Daniel Pryden
23
@DanielPryden Como lector diario de Daily WTF, lo creo.
esponjoso
1
2 ^ 33 = ~ 8.6 mil millones. Para 4 mil millones de valores diferentes solo necesita 32 bits.
un CVn
55
@ MichaelKjörling uno de los 33 es para el 0 por defecto
monstruo de trinquete
@ MichaelKjörling: Para ser justos, solo hay 32 miembros que son poderes de 2, ya que 0 no es un poder de dos. Entonces, "33 miembros, cada uno una potencia de dos" no es exactamente correcto (a menos que cuentes 2 ** -infinitycomo una potencia de dos).
Brian
36

Debido a que estos valores representan ubicaciones de bits únicas en binario:

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

etc., entonces

1 | 2 == binary 00000011

EDITAR:

3 == binary 00000011

3 en binario está representado por un valor de 1 tanto en el lugar de las unidades como en el de los dos. En realidad es lo mismo que el valor 1 | 2. Entonces, cuando intenta usar los lugares binarios como indicadores para representar algún estado, 3 generalmente no tiene sentido (a menos que haya un valor lógico que en realidad sea la combinación de los dos)

Para mayor aclaración, es posible que desee ampliar su enumeración de ejemplo de la siguiente manera:

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

Por lo tanto, en que tengo Permissions.All, también tienen implícitamente Permissions.Read, Permissions.WriteyPermissions.Delete

Chris Shain
fuente
y cual es el problema con 2 | 3?
Pascal
1
@Pascal: Debido a que 3es 11binario, es decir, no se asigna a un solo bit establecido, por lo que pierde la capacidad de asignar 1 bit en una posición arbitraria a un valor significativo.
Ed S.
8
@Pascal dicho de otra manera, 2|3 == 1|3 == 1|2 == 3. Así que si usted tiene un valor con el binario 00000011, y sus banderas incluido los valores 1, 2y 3, a continuación, usted no sabe si ese valor representa 1 and 3, 2 and 3, 1 and 2o only 3. Eso lo hace mucho menos útil.
yshavit
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
}

Creo que escribir así es más fácil de entender y leer, y no es necesario calcularlo.

Topadora
fuente
5

Estos se utilizan para representar banderas de bits que permiten combinaciones de valores de enumeración. Creo que es más claro si escribes los valores en notación hexadecimal

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}
JaredPar
fuente
44
@Pascal: Quizás sea más legible para usted en este momento, pero a medida que adquiera experiencia, la visualización de bytes en hexadecimal se convierte en una segunda naturaleza. Dos dígitos en mapas hexadecimales a mapas de un byte a 8 bits (bueno ... un byte suele ser de 8 bits de todos modos ... no siempre es cierto, pero para este ejemplo está bien generalizar).
Ed S.
55
@Pascal rápido, ¿qué obtienes cuando multiplicas 4194304por 2? ¿Qué tal 0x400000? Es mucho más fácil reconocerlo 0x800000como la respuesta correcta que 8388608, y también es menos propenso a errores al escribir el valor hexadecimal.
phoog
66
Es mucho más fácil saber, de un vistazo, si sus banderas están configuradas correctamente (es decir, son potencias de 2), si usa hex. ¿Es 0x10000un poder de dos? Sí, comienza con 1, 2, 4 u 8 y tiene todos los 0 después. No es necesario traducir mentalmente 0x10 a 16 (aunque hacerlo probablemente se convertirá en una segunda naturaleza eventualmente), solo piense en ello como "un poder de 2".
Brian
1
Estoy absolutamente de acuerdo con Jared sobre que es mucho más fácil notarlo en hexadecimal. solo usa 1 2 4 8 y shift
bevacqua
1
Personalmente, prefiero simplemente usar, por ejemplo, P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. No estoy seguro de si ese tipo de plegado constante funciona en C #, pero funciona bien en C / C ++ (así como en Java, creo).
esponjoso
1

Esto es realmente más un comentario, pero como eso no admitiría el formato, solo quería incluir un método que he empleado para configurar enumeraciones de banderas:

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

Encuentro este enfoque especialmente útil durante el desarrollo en el caso en que le gusta mantener sus banderas en orden alfabético. Si determina que necesita agregar un nuevo valor de indicador, puede insertarlo alfabéticamente y el único valor que debe cambiar es el que ahora precede.

Sin embargo, tenga en cuenta que una vez que se publica una solución en cualquier sistema de producción (especialmente si la enumeración se expone sin un acoplamiento estrecho, como en un servicio web), es muy recomendable no cambiar ningún valor existente dentro de la enumeración.

Mike Guthrie
fuente
1

Muchas buenas respuestas a esta ... solo diré ... si no le gusta o no puede comprender fácilmente lo que la <<sintaxis está tratando de expresar ... Personalmente prefiero una alternativa (y me atrevo a decir, un estilo de declaración de enumeración sencillo ) ...

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

Mucho más fácil (para mí, al menos) de comprender. Alinee los ... describa el resultado que desea, obtenga el resultado que DESEA. No se necesitan "cálculos".

Alex Gray
fuente