Dlaczego uprawnienia wyliczenia często mają wartości 0, 1, 2, 4?

159

Dlaczego ludzie zawsze używają wartości wyliczenia, jak 0, 1, 2, 4, 8lub nie 0, 1, 2, 3, 4?

Czy ma to coś wspólnego z operacjami bitowymi itp.?

Naprawdę byłbym wdzięczny za mały przykładowy fragment pokazujący, jak to jest poprawnie używane :)

[Flags]
public enum Permissions
{
    None   = 0,
    Read   = 1,
    Write  = 2,
    Delete = 4
}
Pascal
źródło
1
możliwy duplikat Enum jako flagi za pomocą, ustawiania i przesuwania
Henk Holterman
25
Nie zgadzam się z podupadłym głosowaniem.
zzzzBov
Sposób ustawiania uprawnień w systemie UNIX również opiera się na tej samej logice.
Rudy
3
@Pascal: Pomocne może być przeczytanie o bitowym OR (i bitowym AND ), który jest tym, co |(i &) reprezentuje. Różne odpowiedzi zakładają, że je znasz.
Brian
2
@IAdapter Rozumiem, dlaczego tak myślisz, ponieważ odpowiedzi na oba pytania są takie same, ale myślę, że pytania są różne. Drugie pytanie dotyczy tylko przykładu lub wyjaśnienia atrybutu Flags w C #. Wydaje się, że to pytanie dotyczy koncepcji flag bitowych i ich podstaw.
Jeremy S

Odpowiedzi:

268

Ponieważ są one potęgą dwojga i mogę to zrobić:

var permissions = Permissions.Read | Permissions.Write;

A może później ...

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

Jest to pole bitowe, w którym każdy ustawiony bit odpowiada pewnemu zezwoleniu (lub czemukolwiek logicznie odpowiada wyliczona wartość). Gdyby zostały zdefiniowane 1, 2, 3, ..., nie byłby w stanie używać operatorów bitowych w ten sposób i uzyskać znaczących wyników. Aby zagłębić się głębiej ...

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

Zauważyłeś tutaj wzór? Jeśli weźmiemy mój oryginalny przykład, tj.

var permissions = Permissions.Read | Permissions.Write;

Następnie...

permissions == 00000011

Widzieć? Zarówno bity, jak Readi Writesą ustawione i mogę to sprawdzić niezależnie (zauważ również, że Deletebit nie jest ustawiony i dlatego ta wartość nie oznacza uprawnień do usuwania).

Pozwala na przechowywanie wielu flag w jednym polu bitów.

Ed S.
źródło
2
@Malcolm: Tak; myEnum.IsSet. Jestem zdania, że ​​jest to zupełnie bezużyteczna abstrakcja i służy tylko do ograniczenia pisania, ale meh
Ed S.
1
Dobra odpowiedź, ale powinieneś wspomnieć, dlaczego zastosowano atrybut Flagi i kiedy nie chciałbyś stosować flag również do niektórych wyliczeń.
Andy
3
@Andy: Właściwie Flagsatrybut robi niewiele więcej, niż tylko daje „ładny druk” iirc. Możesz użyć wartości wyliczeniowej jako flagi niezależnie od obecności atrybutu.
Ed S.
3
@detly: ponieważ jeśli instrukcje w C # wymagają wyrażenia logicznego. 0nie jest false; falsejest false. Możesz jednak pisać if((permissions & Permissions.Write) > 0).
Ed S.
2
Zamiast „podstępnego” (permissions & Permissions.Write) == Permissions.Write, możesz teraz użyćenum.HasFlag()
Louis Kottmann
147

Jeśli nadal nie jest to jasne z innych odpowiedzi, pomyśl o tym w ten sposób:

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

to tylko krótszy sposób na napisanie:

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
} 

Istnieje osiem możliwości, ale można je przedstawić jako kombinacje tylko czterech członków. Gdyby istniało szesnaście możliwości, można by je przedstawić jako kombinacje tylko pięciu członków. Gdyby istniały cztery miliardy możliwości, można by je przedstawić jako kombinację tylko 33 członków! Oczywiście o wiele lepiej jest mieć tylko 33 członków, z których każdy (oprócz zera) ma potęgę dwóch, niż próbować nazwać cztery miliardy elementów w wyliczeniu.

Eric Lippert
źródło
32
+1 za mentalny wizerunek osoby mającej enumcztery miliardy członków. A smutne jest to, że prawdopodobnie ktoś tego próbował.
Daniel Pryden
23
@DanielPryden Jako codzienny czytelnik Daily WTF wierzę w to.
puszysty
1
2 ^ 33 = ~ 8,6 miliarda. Dla 4 miliardów różnych wartości potrzebujesz tylko 32 bitów.
CVn
5
@ MichaelKjörling jeden z 33 jest domyślny dla 0
zapadka freak
@ MichaelKjörling: Szczerze mówiąc, jest tylko 32 członków, którzy mają moc 2, ponieważ 0 nie jest potęgą dwóch. Zatem sformułowanie „33 członków, każdy z nich to potęga dwóch”, nie jest dokładnie poprawne (chyba że liczy się 2 ** -infinityjako potęgę dwóch).
Brian
36

Ponieważ te wartości reprezentują unikalne lokalizacje bitów w systemie binarnym:

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

itd., więc

1 | 2 == binary 00000011

EDYTOWAĆ:

3 == binary 00000011

3 w systemie dwójkowym jest reprezentowane przez wartość 1 zarówno w miejscu jedynek, jak i dwójek. W rzeczywistości jest taka sama jak wartość 1 | 2. Więc kiedy próbujesz użyć miejsc binarnych jako flag do reprezentowania jakiegoś stanu, 3 zazwyczaj nie ma znaczenia (chyba że istnieje wartość logiczna, która w rzeczywistości jest kombinacją tych dwóch)

Aby uzyskać dalsze wyjaśnienia, możesz chcieć rozszerzyć przykładowe wyliczenie w następujący sposób:

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

Dlatego w mam Permissions.All, ja też pośrednio mają Permissions.Read, Permissions.WriteiPermissions.Delete

Chris Shain
źródło
a jaki jest problem z 2 | 3?
Pascal
1
@Pascal: Ponieważ 3jest 11binarny, tj. Nie mapuje na pojedynczy ustawiony bit, więc tracisz możliwość mapowania 1 bitu w dowolnej pozycji na znaczącą wartość.
Ed S.
8
@Pascal Innymi słowy, 2|3 == 1|3 == 1|2 == 3. Więc jeśli mają wartość binarnego 00000011, a twoje flagi włączone wartości 1, 2oraz 3, to nie wiem, czy wartość ta oznacza 1 and 3, 2 and 3, 1 and 2lub only 3. To sprawia, że ​​jest o wiele mniej przydatny.
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
}

Myślę, że pisanie w ten sposób jest łatwiejsze do zrozumienia i czytania i nie musisz tego obliczać.

Dozer
źródło
5

Są one używane do reprezentowania flag bitowych, które pozwalają na kombinacje wartości wyliczeniowych. Myślę, że jest to wyraźniejsze, jeśli napiszesz wartości w notacji szesnastkowej

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}
JaredPar
źródło
4
@Pascal: Być może jest to dla ciebie bardziej czytelne w tym momencie, ale kiedy zdobędziesz doświadczenie, przeglądanie bajtów w hex staje się drugą naturą. Dwie cyfry w mapowaniu szesnastkowym na jeden bajt odwzorowuje na 8 bitów (no cóż ... bajt i tak to zwykle 8 bitów ... nie zawsze prawda, ale w tym przykładzie można uogólniać).
Ed S.
5
@Pascal szybko, co otrzymujesz mnożąc 4194304przez 2? A co powiesz 0x400000? O wiele łatwiej jest rozpoznać 0x800000poprawną odpowiedź niż 8388608wpisywanie wartości szesnastkowej, a także mniej podatne na błędy.
phoog
6
Na pierwszy rzut oka o wiele łatwiej jest stwierdzić, czy twoje flagi są ustawione poprawnie (tj. Mają potęgę 2), jeśli używasz hex. Czy 0x10000potęga dwóch? Tak, zaczyna się od 1, 2, 4 lub 8, a potem ma wszystkie 0. Nie musisz mentalnie tłumaczyć 0x10 na 16 (chociaż prawdopodobnie stanie się to w końcu drugą naturą), po prostu pomyśl o tym jako o „pewnej mocy 2”.
Brian
1
Jestem absolutnie z Jaredem o tym, że łatwiej jest to zauważyć w heksie. wystarczy użyć 1 2 4 8 i przesunąć
bevacqua
1
Osobiście wolę po prostu używać np. P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. Nie jestem pewien, czy tego rodzaju składanie stałych działa w C #, ale działa dobrze w C / C ++ (a także, jak sądzę, w Javie).
puszysty
1

To jest bardziej komentarz, ale ponieważ nie obsługuje to formatowania, chciałem po prostu dołączyć metodę, której użyłem do ustawiania wyliczeń flag:

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

Uważam, że to podejście jest szczególnie pomocne podczas programowania w przypadku, gdy chcesz zachować swoje flagi w porządku alfabetycznym. Jeśli zdecydujesz, że musisz dodać nową wartość flagi, możesz po prostu wstawić ją alfabetycznie, a jedyną wartością, którą musisz zmienić, jest ta, którą teraz poprzedza.

Należy jednak pamiętać, że po opublikowaniu rozwiązania w dowolnym systemie produkcyjnym (zwłaszcza jeśli wyliczenie jest ujawniane bez ścisłego powiązania, na przykład w usłudze sieci Web), zdecydowanie odradza się zmienianie jakiejkolwiek istniejącej wartości w wyliczeniu.

Mike Guthrie
źródło
1

Wiele dobrych odpowiedzi na to pytanie… powiem tylko… jeśli nie podoba ci się lub nie możesz łatwo pojąć, co <<próbuje wyrazić składnia… Osobiście wolę alternatywę (i ośmielę się powiedzieć, prosty styl deklaracji wyliczenia) …

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

O wiele łatwiejsze (przynajmniej dla mnie) do zrozumienia. Ułóż je w jednej linii… opisz pożądany rezultat, uzyskaj wynik, którego CHCESZ .. Nie są potrzebne żadne „obliczenia”.

Alex Gray
źródło