Dlaczego wyliczenia flag są zwykle definiowane za pomocą wartości szesnastkowych

123

Wiele razy widzę deklaracje wyliczeń flag, które używają wartości szesnastkowych. Na przykład:

[Flags]
public enum MyEnum
{
    None  = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4,
    Flag4 = 0x8,
    Flag5 = 0x10
}

Kiedy deklaruję wyliczenie, zwykle deklaruję to w ten sposób:

[Flags]
public enum MyEnum
{
    None  = 0,
    Flag1 = 1,
    Flag2 = 2,
    Flag3 = 4,
    Flag4 = 8,
    Flag5 = 16
}

Czy istnieje powód lub uzasadnienie, dlaczego niektórzy ludzie wybierają zapisywanie wartości w systemie szesnastkowym, a nie dziesiętnym? Z mojego punktu widzenia łatwiej się pomylić, używając wartości szesnastkowych i przypadkowo Flag5 = 0x16zamiast pisać Flag5 = 0x10.

Adi Lester
źródło
4
Co sprawiłoby, że byłoby mniej prawdopodobne, że napiszesz, 10zamiast 0x10używać liczb dziesiętnych? Zwłaszcza, że ​​mamy do czynienia z liczbami binarnymi, a hex jest trywialnie zamienialne na / z binarnego? 0x111jest o wiele mniej denerwujące w tłumaczeniu w głowie niż 273...
cHao
4
Szkoda, że ​​C # nie ma składni, która nie wymaga jawnie zapisywania potęg dwóch.
Colonel Panic
Robisz tu coś bezsensownego. Celem flag jest to, że będą one połączone bitowo. Ale kombinacje bitowe nie są elementami typu. Wartość Flag1 | Flag2to 3, a 3 nie odpowiada żadnej wartości domeny MyEnum.
Kaz,
Gdzie to widzisz? z odbłyśnikiem?
giammin
@giammin To ogólne pytanie, a nie konkretna implementacja. Możesz na przykład wziąć projekty open source lub po prostu kod dostępny w sieci.
Adi Lester

Odpowiedzi:

184

Racjonalne uzasadnienia mogą się różnić, ale widzę zaletę, że szesnastkowy przypomina: „OK, nie mamy już do czynienia z liczbami w arbitralnym, wymyślonym przez ludzi świecie o podstawie dziesiątki. Mamy do czynienia z bitami - światem maszyny - i będziemy grać według jego zasad ”. Szesnastkowy jest rzadko używany, chyba że masz do czynienia z tematami stosunkowo niskiego poziomu, w których liczy się układ pamięci. Używanie go wskazuje na fakt, że w takiej sytuacji jesteśmy teraz.

Ponadto nie jestem pewien co do języka C #, ale wiem, że w języku C x << yjest to poprawna stała czasu kompilacji. Korzystanie z przesunięć bitowych wydaje się najbardziej jasne:

[Flags]
public enum MyEnum
{
    None  = 0,
    Flag1 = 1 << 0,
    Flag2 = 1 << 1,
    Flag3 = 1 << 2,
    Flag4 = 1 << 3,
    Flag5 = 1 << 4
}
istnieje dla wszystkich
źródło
7
To bardzo interesujące iw rzeczywistości jest to również prawidłowe wyliczenie C #.
Adi Lester
13
+1 z tym zapisem nigdy nie popełnisz błędu w obliczeniach wartości wyliczeniowej
Sergey Berezovskiy
1
@AllonGuralnek: Czy kompilator przypisuje unikalne pozycje bitów na podstawie adnotacji [Flags]? Zwykle zaczyna się od 0 i zwiększa się o 1, więc każda wartość wyliczenia przypisana 3 (dziesiętnie) będzie miała wartość binarną 11, ustawiając dwa bity.
Eric J.
2
@Eric: Huh, nie wiem dlaczego, ale zawsze byłem pewien, że przypisuje wartości potęgi dwa. Właśnie sprawdziłem i chyba się myliłem.
Allon Guralnek
38
Kolejny fajny fakt z x << ynotacją. 1 << 10 = KB, 1 << 20 = MB, 1 << 30 = GBI tak dalej. To naprawdę fajne, jeśli chcesz zrobić 16-kilobajtową tablicę dla bufora, który możesz po prostu iśćvar buffer = new byte[16 << 10];
Scott Chamberlain,
43

Dzięki temu łatwo zauważyć, że są to flagi binarne .

None  = 0x0,  // == 00000
Flag1 = 0x1,  // == 00001
Flag2 = 0x2,  // == 00010
Flag3 = 0x4,  // == 00100
Flag4 = 0x8,  // == 01000
Flag5 = 0x10  // == 10000

Chociaż postęp czyni to jeszcze wyraźniejszym:

Flag6 = 0x20  // == 00100000
Flag7 = 0x40  // == 01000000
Flag8 = 0x80  // == 10000000
Oded
źródło
3
Właściwie dodaję 0 przed 0x1, 0x2, 0x4, 0x8 ... Więc otrzymuję 0x01, 0x02, 0x04, 0x08 i 0x10 ... Uważam, że jest to łatwiejsze do odczytania. Czy ja coś zepsuję?
LightStriker
4
@Light - wcale nie. Jest to bardzo częste, więc możesz zobaczyć, jak się wyrównują. Po prostu sprawia, że ​​bity są bardziej wyraźne :)
Oded
2
@LightStriker po prostu go wyrzuci, co ma znaczenie, jeśli nie używasz hex. Wartości, które zaczynają się tylko od zera, są interpretowane jako ósemkowe. Tak 012jest w rzeczywistości 10.
Jonathon Reinhart
@JonathonRainhart: To wiem. Ale zawsze używam hex, kiedy używam bitfields. Nie jestem pewien, czy intzamiast tego czułbym się bezpiecznie . Wiem, że to głupie ... ale nawyki ciężko umierają.
LightStriker
36

Myślę, że dzieje się tak dlatego, że sekwencja zawsze wynosi 1, 2, 4, 8, a następnie dodaj 0.
Jak widać:

0x1 = 1 
0x2 = 2
0x4 = 4
0x8 = 8
0x10 = 16
0x20 = 32
0x40 = 64
0x80 = 128
0x100 = 256
0x200 = 512
0x400 = 1024
0x800 = 2048

i tak dalej, o ile pamiętasz sekwencję 1-2-4-8, możesz zbudować wszystkie kolejne flagi bez konieczności pamiętania o potęgach 2

VRonin
źródło
13

Ponieważ [Flags]oznacza, że ​​wyliczenie to naprawdę pole bitowe . Ze [Flags]można użyć bitowego AND ( &) i OR ( |) Operatorzy połączyć flagi. W przypadku takich wartości binarnych prawie zawsze bardziej oczywiste jest użycie wartości szesnastkowych. To jest właśnie powód, dla którego używamy szesnastkowego w pierwszej kolejności. Każdy znak szesnastkowy odpowiada dokładnie jednemu półbajtowi (cztery bity). W przypadku liczb dziesiętnych to mapowanie od 1 do 4 nie jest prawdziwe.

Jonathon Reinhart
źródło
2
Możliwość korzystania z operacji bitowych nie ma właściwie nic wspólnego z atrybutem flag.
Mattias Nordqvist,
4

Ponieważ istnieje mechaniczny, prosty sposób na podwojenie potęgi dwóch w hex. W liczbach dziesiętnych jest to trudne. Wymaga długiego mnożenia w twojej głowie. W hex to prosta zmiana. Możesz to zrobić aż do momentu, w 1UL << 63którym nie możesz tego zrobić w systemie dziesiętnym.

usr
źródło
1
Myślę, że to ma największy sens. W przypadku wyliczeń z dużym zestawem wartości jest to najłatwiejsza droga (z możliwym wyjątkiem przykładu @ Hypercube).
Adi Lester
3

Ponieważ łatwiej jest śledzić ludziom, gdzie bity są we fladze. Każda cyfra szesnastkowa może pasować do 4-bitowej liczby dwójkowej.

0x0 = 0000
0x1 = 0001
0x2 = 0010
0x3 = 0011

... and so on

0xF = 1111

Zazwyczaj chcesz, aby twoje flagi nie nakładały się na bity, najłatwiejszym sposobem zrobienia tego i wizualizacji jest użycie wartości szesnastkowych do deklarowania flag.

Tak więc, jeśli potrzebujesz flag z 16 bitami, użyjesz 4-cyfrowych wartości szesnastkowych i w ten sposób unikniesz błędnych wartości:

0x0001 //= 1 = 000000000000 0001
0x0002 //= 2 = 000000000000 0010
0x0004 //= 4 = 000000000000 0100
0x0008 //= 8 = 000000000000 1000
...
0x0010 //= 16 = 0000 0000 0001 0000
0x0020 //= 32 = 0000 0000 0010 0000
...
0x8000 //= 32768 = 1000 0000 0000 0000
Tylko ty
źródło
To wyjaśnienie jest dobre ... jedyne, czego brakuje, to binarny odpowiednik pokazujący ostateczne zestawienie bitu,
skubania