Чому дозволи enum часто мають значення 0, 1, 2, 4?


159

Чому люди завжди використовують значення enum, як 0, 1, 2, 4, 8і ні 0, 1, 2, 3, 4?

Це щось пов’язане з бітовими операціями тощо?

Я дуже вдячний невеликим фрагментом зразка про те, як це правильно використовується :)

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


25
Я не згоден у голосуванні.
zzzzBov

Спосіб встановлення дозволу UNIX також заснований на тій же логіці.
Руді

3
@Pascal: Вам може бути корисно прочитати про Bitwise АБОпобітові AND ), що саме |&) являє собою. Різні відповіді припускають, що ви з цим знайомі.
Брайан

2
@IAdapter Я можу зрозуміти, чому ви могли б подумати про це, оскільки відповідь на обидва однакові, але я думаю, що питання різні. Інше питання просто запитує приклад або пояснення атрибута "Прапори" в C #. Це питання, здається, стосується поняття бітових прапорів та основ, які стоять за ними.
Джеремі S

Відповіді:


268

Тому що вони є двома силами, і я можу це зробити:

var permissions = Permissions.Read | Permissions.Write;

І, можливо, пізніше ...

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

Це бітове поле, де кожному встановленому біту відповідає якийсь дозвіл (або все, що логічно відповідає переліченому значенню). Якби вони були визначені так, 1, 2, 3, ...ви б не змогли таким чином використовувати побітові оператори та отримати значущі результати. Поглибитися глибше ...

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

Помітили тут шаблон? Тепер якщо ми візьмемо мій оригінальний приклад, тобто

var permissions = Permissions.Read | Permissions.Write;

Тоді...

permissions == 00000011

Побачити? І біти, Readі Writeбіти встановлені, і я можу перевірити це незалежно (Також зауважте, що Deleteбіт не встановлено, і тому це значення не містить дозволу на видалення).

Це дозволяє зберігати кілька прапорів в одному полі біт.


2
@Malcolm: Це так; myEnum.IsSet. Я вважаю, що це абсолютно марна абстракція і служить лише для скорочення набору тексту, але мені
Ед С.

1
Хороша відповідь, але ви повинні зазначити, чому застосовується атрибут Flags, і коли ви не хочете застосовувати прапори також до деяких перерахунків.
Енді

3
@Andy: Насправді Flagsатрибут робить трохи більше, ніж дає вам «гарненьку друк» iirc. Ви можете використовувати перелічене значення як прапор незалежно від наявності атрибута.
Ред С.

3
@detly: Тому що, якщо заяви в C # вимагають булевого вираження. 0не є false; falseє false. Однак ви можете написати if((permissions & Permissions.Write) > 0).
Ред С.

2
Замість " (permissions & Permissions.Write) == Permissions.Writeenum.HasFlag()
хитрості"

147

Якщо з інших відповідей все ще не зрозуміло, подумайте про це так:

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

це лише коротший спосіб написати:

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
} 

Існує вісім можливостей, але ви можете представити їх у вигляді комбінацій лише чотирьох членів. Якщо було шістнадцять можливостей, то ви могли б представити їх як комбінації лише з п'яти членів. Якби було чотири мільярди можливостей, то ви могли б представити їх як комбінації лише з 33 членів! Очевидно, що набагато краще мати лише 33 члена, кожен (крім нуля) потужністю два, ніж намагатися назвати чотири мільярди предметів у перерахунку.


32
+1 для ментального образу enumчотирьох мільярдів членів. І сумна частина - це, мабуть, хтось там спробував.
Даніель Приден

23
@DanielPryden Як щоденний читач Daily WTF, я б у це повірив.
пухнастий

1
2 ^ 33 = ~ 8,6 млрд. Для 4 мільярдів різних значень вам потрібно лише 32 біти.
CVn

5
@ MichaelKjörling один з 33 знаходиться в 0 по замовчуванням
храповим урод

@ MichaelKjörling: Для справедливості, є лише 32 члени, які мають повноваження 2, оскільки 0 - це не сила двох. Отже, "33 члени, кожен - сила два" не є точно правильним (якщо ви не рахуєтесь 2 ** -infinityяк сила двох).
Брайан

36

Оскільки ці значення представляють унікальні бітові локації у двійковій формі:

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

тощо, так

1 | 2 == binary 00000011

Редагувати:

3 == binary 00000011

3 у двійковій формі представлено значенням 1 як у тому, і в другому місці. Це насправді те саме, що значення 1 | 2. Отже, коли ви намагаєтесь використовувати двійкові місця як прапори для представлення якогось стану, 3 зазвичай не має сенсу (якщо тільки немає логічного значення, яке насправді є поєднанням двох)

Для подальшого роз'яснення ви можете розширити свій приклад enum таким чином:

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

Тому в мене Permissions.All, я також маю неявний Permissions.Read, Permissions.WriteіPermissions.Delete


а яка проблема з 2 | 3?
Паскаль

1
@Pascal: Оскільки 3є 11бінарним, тобто він не відображається в одному наборі біт, тому ви втрачаєте можливість відображати 1 біт у довільному положенні на значуще значення.
Ред С.

8
@Pascal інших слів, 2|3 == 1|3 == 1|2 == 3. Так що якщо у вас є значення з двійковій 00000011, і ваші прапори включені значення 1, 2і 3, то ви не знаєте , якщо це значення являє собою 1 and 3, 2 and 3, 1 and 2або only 3. Це робить його набагато менш корисним.
ішавіт

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
}

Я думаю, що писати так, як це легше зрозуміти і прочитати, і вам не потрібно це обчислювати.


5

Вони використовуються для представлення бітових прапорів, що дозволяє комбінувати значення перерахунків. Я думаю, що зрозуміліше, якщо ви пишете значення в шістнадцятковій нотації

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

4
@Pascal: Можливо, для вас це читає в даний момент часу, але по мірі набуття досвіду перегляд байтів у шістнадцятковій формі стає другою природою. Дві цифри в шістнадцяткових картах на одну байтну карту на 8 біт (ну ... як правило, байт все-таки 8 біт ... не завжди правда, але для цього прикладу добре узагальнити).
Ред С.

5
@Pascal швидко, що ви отримуєте, коли множите 4194304на 2? Як щодо 0x400000? Набагато простіше розпізнати 0x800000правильну відповідь 8388608, а також вводити шістнадцяткове значення менш схильне до помилок.
фог

6
На перший погляд набагато простіше сказати, якщо ваші прапори встановлені належним чином (тобто чи є сили 2), якщо ви використовуєте шістнадцятковий. Це 0x10000сила двох? Так, він починається з 1, 2, 4 або 8 і після цього має всі 0. Вам не потрібно подумки переводити 0x10 на 16 (хоча це, мабуть, з часом стане другою природою), просто подумайте про це як "деяку силу 2".
Брайан

1
Я абсолютно з Джаредом з приводу того, що це набагато простіше помітити в шістнадцятковій формі. ви просто використовуєте 1 2 4 8 і
змініть

1
Особисто я вважаю за краще використовувати, наприклад, P_READ = 1 << 0, P_WRITE = 1 <, 1, P_RW = P_READ | P_WRITE. Я не впевнений, чи працює такий тип постійного складання в C #, але він працює добре на C / C ++ (як і на Java, я думаю).
пухнастий

1

Це дійсно більше коментаря, але оскільки це не підтримує форматування, я просто хотів включити метод, який я застосував для встановлення переліку прапорців:

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

Я вважаю цей підхід особливо корисним під час розробки в тому випадку, коли ви хочете підтримувати свої прапори в алфавітному порядку. Якщо ви визначите, що вам потрібно додати нове значення прапора, ви можете просто вставити його в алфавітному порядку, і єдине значення, яке вам потрібно змінити, - це те, якому воно передує.

Однак зауважте, що після публікації рішення в будь-якій виробничій системі (особливо, якщо перерахунок виставляється без жорсткої зв'язку, наприклад, через веб-сервіс), то вкрай доцільно проти зміни будь-якого наявного значення в перерахунку.


1

Багато відповідей на це… Я просто скажу .. якщо вам не подобається, або ви не можете легко зрозуміти те, що <<намагається висловити синтаксис. Я особисто віддаю перевагу альтернативі (і, смію сказати, прямому стилю декларації перерахунку) …

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

Так набагато простіше (як мінімум для мене) зрозуміти. Складіть ті, що описують… опишіть бажаний результат, отримайте той результат, якого ХОЧЕТЕ. Не потрібно «розрахунків».

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.