Perché le autorizzazioni enum hanno spesso valori 0, 1, 2, 4?


159

Perché le persone usano sempre i valori enum come 0, 1, 2, 4, 8e no 0, 1, 2, 3, 4?

Ha qualcosa a che fare con le operazioni sui bit, ecc.?

Gradirei davvero un piccolo frammento di esempio su come questo viene utilizzato correttamente :)

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


25
Non sono d'accordo sul voto doppio.
zzzzBov

Anche il modo UNIX per impostare le autorizzazioni si basa sulla stessa logica.
Rudy,

3
@Pascal: potresti trovare utile leggere Bitwise OR (e Bitwise AND ), che è ciò che |(e &) rappresentano. Le varie risposte presuppongono che tu ne abbia familiarità.
Brian,

2
@IAdapter Posso capire perché lo pensi, dato che la risposta a entrambi è la stessa, ma penso che le domande siano diverse. L'altra domanda richiede solo un esempio o una spiegazione dell'attributo Flags in C #. Questa domanda sembra riguardare il concetto di bit flags e i fondamenti dietro di essi.
Jeremy S,

Risposte:


268

Perché sono poteri di due e posso farlo:

var permissions = Permissions.Read | Permissions.Write;

E forse più tardi ...

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

È un campo di bit, in cui ogni bit impostato corrisponde a qualche autorizzazione (oa qualunque valore logico corrispondente corrisponda logicamente). Se questi fossero definiti come non 1, 2, 3, ...si sarebbe in grado di utilizzare operatori bit per bit in questo modo e ottenere risultati significativi. Per approfondire ...

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

Notare uno schema qui? Ora se prendiamo il mio esempio originale, cioè

var permissions = Permissions.Read | Permissions.Write;

Poi...

permissions == 00000011

Vedere? Entrambi i bit Reade Writesono impostati e posso verificarlo in modo indipendente (Notare anche che il Deletebit non è impostato e quindi questo valore non trasmette l'autorizzazione per l'eliminazione).

Permette di memorizzare più flag in un singolo campo di bit.


2
@Malcolm: Lo fa; myEnum.IsSet. Sono dell'opinione che questa è un'astrazione completamente inutile e serve solo a ridurre la digitazione, ma meh
Ed S.

1
Buona risposta, ma dovresti menzionare il motivo per cui viene applicato l'attributo Flags e quando non vorrai applicare le Flags anche ad alcuni enumeratori.
Andy,

3
@Andy: In realtà, l' Flagsattributo non fa altro che darti un iirc di 'bella stampa'. È possibile utilizzare un valore enumerato come flag indipendentemente dalla presenza dell'attributo.
Ed S.

3
@detly: perché se le istruzioni in C # richiedono un'espressione booleana. 0non lo è false; falselo è false. Puoi comunque scrivere if((permissions & Permissions.Write) > 0).
Ed S.

2
Invece del "difficile" (permissions & Permissions.Write) == Permissions.Write, ora puoi usareenum.HasFlag()
Louis Kottmann il

147

Se non è ancora chiaro dalle altre risposte, pensaci in questo modo:

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

è solo un modo più breve per scrivere:

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
} 

Esistono otto possibilità, ma puoi rappresentarle come combinazioni di soli quattro membri. Se ci fossero sedici possibilità, potresti rappresentarle come combinazioni di soli cinque membri. Se ci fossero quattro miliardi di possibilità, potresti rappresentarle come combinazioni di soli 33 membri! È ovviamente molto meglio avere solo 33 membri, ciascuno (tranne zero) una potenza di due, piuttosto che cercare di nominare quattro miliardi di oggetti in un enum.


32
+1 per l'immagine mentale di un enumcon quattro miliardi di membri. E la parte triste è che probabilmente qualcuno là fuori l'ha provato.
Daniel Pryden,

23
@DanielPryden Come lettore quotidiano del Daily WTF, ci credo.
soffice

1
2 ^ 33 = ~ 8.6 miliardi. Per 4 miliardi di valori diversi sono necessari solo 32 bit.
un CVn il

5
@ MichaelKjörling uno dei 33 è per impostazione predefinita 0
maniaco del cricchetto

@ MichaelKjörling: Per essere onesti, ci sono solo 32 membri che hanno poteri di 2, poiché 0 non è un potere di due. Quindi "33 membri, ciascuno con un potere di due" non è esattamente corretto (a meno che non si contenga 2 ** -infinitycome un potere di due).
Brian,

36

Poiché questi valori rappresentano posizioni di bit univoche in binario:

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

ecc

1 | 2 == binary 00000011

MODIFICARE:

3 == binary 00000011

3 in binario è rappresentato da un valore di 1 sia nella posizione uno che nella posizione due. In realtà è lo stesso del valore 1 | 2. Pertanto, quando si tenta di utilizzare i posti binari come flag per rappresentare un certo stato, 3 non è in genere significativo (a meno che non esista un valore logico che sia effettivamente la combinazione dei due)

Per ulteriori chiarimenti, potresti voler estendere il tuo esempio come segue:

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

Pertanto nel Ho Permissions.All, ho anche implicitamente avere Permissions.Read, Permissions.WriteePermissions.Delete


e qual è il problema con 2 | 3?
Pascal

1
@Pascal: poiché 3è 11binario, cioè non viene mappato su un singolo bit impostato, quindi si perde la capacità di mappare 1 bit in una posizione arbitraria su un valore significativo.
Ed S.

8
@Pascal dirla in altro modo, 2|3 == 1|3 == 1|2 == 3. Quindi, se avete un valore con binario 00000011, e le vostre bandiere inclusi valori 1, 2e 3, allora si sarebbe non so se questo valore rappresenta 1 and 3, 2 and 3, 1 and 2o only 3. Questo lo rende molto meno utile.
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
}

Penso che scrivere in questo modo sia più facile da capire e da leggere, e non è necessario calcolarlo.


5

Questi sono usati per rappresentare flag di bit che consentono combinazioni di valori enum. Penso che sia più chiaro se scrivi i valori in notazione esadecimale

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}

4
@Pascal: forse è più leggibile per te in questo momento, ma man mano che acquisisci esperienza la visualizzazione di byte in esadecimale diventa una seconda natura. Due cifre in mappe esadecimali a mappe a un byte a 8 bit (beh ... un byte di solito è comunque 8 bit ... non sempre vero, ma per questo esempio è ok generalizzare).
Ed S.

5
@Pascal veloce, cosa ottieni quando moltiplichi 4194304per 2? Che ne dici 0x400000? È molto più facile riconoscere 0x800000la risposta corretta di 8388608, ed è anche meno soggetto a errori nel digitare il valore esadecimale.
phoog

6
È molto più facile dire, a colpo d'occhio, se le tue bandiere sono impostate correttamente (cioè, sono potenze di 2), se usi hex. Il 0x10000potere è due? Sì, inizia con 1, 2, 4 o 8 e in seguito ha tutti gli 0. Non è necessario tradurre mentalmente da 0x10 a 16 (anche se alla fine probabilmente diventerà una seconda natura), basti pensare a questo come "un potere di 2".
Brian,

1
Sono assolutamente d'accordo con Jared sul fatto che sia molto più facile notare in esadecimale. basta usare 1 2 4 8 e cambiare
bevacqua il

1
Personalmente, preferisco usare ad esempio P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. Non sono sicuro che questo tipo di piegatura costante funzioni in C #, ma funziona perfettamente in C / C ++ (oltre a Java, credo).
soffice

1

Questo è davvero più di un commento, ma dal momento che non supporta la formattazione, volevo solo includere un metodo che ho usato per impostare le enumerazioni di flag:

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

Trovo questo approccio particolarmente utile durante lo sviluppo nel caso in cui ti piace mantenere le tue bandiere in ordine alfabetico. Se decidi che devi aggiungere un nuovo valore di bandiera, puoi semplicemente inserirlo in ordine alfabetico e l'unico valore che devi modificare è quello che ora precede.

Si noti, tuttavia, che una volta che una soluzione viene pubblicata su qualsiasi sistema di produzione (specialmente se l'enum viene esposto senza un accoppiamento stretto, ad esempio tramite un servizio Web), è consigliabile sconsigliare di modificare qualsiasi valore esistente all'interno dell'enum.


1

Molte buone risposte a questa ... dirò solo .. se non ti piace, o non riesci facilmente a capire ciò che la <<sintassi sta cercando di esprimere .. Personalmente preferisco un'alternativa (e oserei dire, semplice stile di dichiarazione 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

Molto più facile (almeno per me) da capire. Allinea quelli ... descrivi il risultato che desideri, ottieni il risultato che VUOI. Non sono necessari "calcoli".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.