Почему разрешения enum часто имеют значения 0, 1, 2, 4?

Почему люди всегда используют значения перечисления, такие как 0, 1, 2, 4, 8, а не 0, 1, 2, 3, 4?

Это как-то связано с битовыми операциями и т. д.?

Я был бы очень признателен за небольшой пример того, как это правильно используется :)

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

person Pascal    schedule 21.03.2012    source источник
comment
возможный дубликат Enum as Flag с использованием, установкой и сдвигом   -  person Henk Holterman    schedule 22.03.2012
comment
Я не согласен с голосованием за обман.   -  person zzzzBov    schedule 22.03.2012
comment
Способ UNIX для установки разрешения также основан на той же логике.   -  person Rudy    schedule 22.03.2012
comment
@Pascal: возможно, вам будет полезно прочитать о побитовом ИЛИПобитовое AND ), что и представляют |&). Различные ответы предполагают, что вы знакомы с ним.   -  person Brian    schedule 22.03.2012
comment
@Pascal: Если вы не используете степени 2, если у вас есть, скажем, 4, вы не сможете отличить 1 + 3 от простого 4   -  person Mosty Mostacho    schedule 22.03.2012
comment
возможный дубликат атрибута Enum Flags   -  person IAdapter    schedule 28.03.2012
comment
@IAdapter Я понимаю, почему вы так думаете, поскольку ответы на оба вопроса одинаковы, но я думаю, что вопросы разные. Другой вопрос просто требует примера или объяснения атрибута Flags в C#. Кажется, этот вопрос касается концепции битовых флагов и их основ.   -  person Jeremy S    schedule 28.03.2012


Ответы (7)


Потому что они степени двойки, и я могу сделать это:

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 не установлен, и поэтому это значение не дает разрешения на удаление).

Это позволяет хранить несколько флагов в одном поле битов.

person Ed S.    schedule 21.03.2012
comment
Как примечание, в Java коллекция перечислений — это то, где EnumSet пригодится вместо битового поля. В C# наверняка есть аналог, возможно, его стоит упомянуть в ответе. - person Malcolm; 22.03.2012
comment
@Malcolm: Это так; myEnum.IsSet. Я придерживаюсь мнения, что это совершенно бесполезная абстракция и служит только для сокращения набора текста, но мех - person Ed S.; 22.03.2012
comment
Хороший ответ, но вы должны указать, почему применяется атрибут Flags, и когда вы не хотите применять Flags к некоторым перечислениям. - person Andy; 22.03.2012
comment
@Andy: На самом деле атрибут Flags делает немного больше, чем дает вам «красивую печать» iirc. Вы можете использовать перечисляемое значение в качестве флага независимо от наличия атрибута. - person Ed S.; 22.03.2012
comment
Не совсем точно. С# может быть все равно, но попытка использовать перечисления без флагов в VB, похоже, не работает: stackoverflow.com/questions/5902967/ - person Andy; 22.03.2012
comment
@Andy: Ну, я не использую VB, так что не знаю. Я дал ответ на С#, потому что вопрос помечен как С#, но интересно, что они будут отличаться. - person Ed S.; 22.03.2012
comment
Почему бы не просто permissions & Permissions.Write? (Я действительно не знаю С#, только С, так что, может быть, это разница в языке?) - person detly; 22.03.2012
comment
@detly: потому что операторы if в C# требуют логического выражения. 0 не false; false это false. Однако вы можете написать if((permissions & Permissions.Write) > 0). - person Ed S.; 22.03.2012
comment
@ЭдС. Ах, конечно, я все еще думал о C, где нет настоящего логического типа. - person detly; 22.03.2012
comment
@ЭдС. Джош Блох не согласен с вами в пункте 32 Эффективной Java. Это не только менее трудоемкий процесс, но и менее подверженный ошибкам и более простой в использовании, потому что вместо работы с побитовыми операциями вы работаете с коллекциями. - person Malcolm; 22.03.2012
comment
@ЭдС. Понятно, но хотя вопрос касается C #, это не означает, что плакат не делает то, что должно быть жалобой CLS и видимым на других языках. - person Andy; 22.03.2012
comment
@Malcolm: Что ж, согласен со мной Джош Блох или нет, это личное мнение, и я не вижу смысла скрывать такую ​​простую операцию за методом. Эта операция никогда не изменится, и, честно говоря, любой приличный программист должен иметь возможность И с парой переменных без проблем. Я не покупаюсь на менее подверженный ошибкам аргумент. - person Ed S.; 22.03.2012
comment
@ЭдС. Для И несколько констант не проблема, но как насчет добавления диапазона флагов, перебора набора флагов или печати? А также ничто не мешает добавить в битовое поле неправильную константу, потому что нет проверки типов. Поэтому я не вижу никаких преимуществ в использовании битовых полей, за исключением случаев, когда вам, например, нужно работать с сериализацией. Это всего лишь предложение, конечно, ответ за вами, и вам не нужно что-то добавлять, основываясь на моем мнении. - person Malcolm; 22.03.2012
comment
@Malcolm: Наверное, я не понимаю. Добавление диапазона флагов? В порядке; flags = Flag.First | Flag.Second | Flag.Third. Не сложно и не более подвержено ошибкам, чем вызов метода. Как метод предотвращает добавление неправильного значения? я тоже этого не понимаю; Поэтому я не вижу никаких преимуществ в использовании битовых полей, за исключением случаев, когда вам нужно работать с сериализацией. Правда? Вы бы предпочли определить свойство типа для каждого возможного значения? Я не понимаю. - person Ed S.; 22.03.2012
comment
@ЭдС. Добавление диапазона флагов: это для трех из них, что, если у вас их намного больше? Попробуйте заменить EnumSet.range(Flag.FIRST, Flag.FOURTEENTH). Предотвращение неправильных значений: у вас есть EnumSet<Flag> field и у вас есть enum Flag и AnotherFlag. Вы не можете добавлять константы из AnotherFlag в поле, но могли бы, если бы у вас было int field. Что касается свойств, вам не нужно определять какие-либо свойства, вы только заменяете битовое поле на EnumSet. Может я не совсем понял вашу мысль, можно поподробнее? - person Malcolm; 22.03.2012
comment
@Malcolm: Хорошо, сначала я неправильно понял тебя, так как не знаю Java. Я думал, что BitSet(somFlag) — это функция, которая возвращает логическое значение в зависимости от того, присутствует ли значение, то есть if(permissions.BitSet(someValue)). На самом деле это представление набора битов, где от вас абстрагируется не только проверка, описанная выше. - person Ed S.; 22.03.2012
comment
@ЭдС. Да, точно. В свою очередь, я не очень хорошо знаком с C#, поэтому не знаю, применимо ли здесь мое предложение, основанное на Java. Рады, что устранили это недоразумение. - person Malcolm; 23.03.2012
comment
Вместо «хитрого» (permissions & Permissions.Write) == Permissions.Write теперь можно использовать enum.HasFlag() - person Louis Kottmann; 23.10.2012
comment
@Baboon: Да, это более лаконично, что приятно, но вам придется постараться, чтобы убедить меня, что базовые побитовые операции сложны :) - person Ed S.; 23.10.2012
comment
@ЭдС. Отсюда и кавычки ;) но согласись, что в первый раз это не так очевидно. - person Louis Kottmann; 24.10.2012

Если это все еще не ясно из других ответов, подумайте об этом так:

[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 члена, каждый (кроме нуля) в степени двойки, чем пытаться назвать четыре миллиарда элементов в перечислении.

person Eric Lippert    schedule 21.03.2012
comment
+1 за мысленный образ enum с четырьмя миллиардами участников. И что самое печальное, возможно, кто-то уже пробовал это. - person Daniel Pryden; 22.03.2012
comment
@DanielPryden Как ежедневный читатель Daily WTF, я бы в это поверил. - person fluffy; 22.03.2012
comment
2 ^ 33 = ~ 8,6 миллиарда. Для 4 миллиардов различных значений вам нужно всего 32 бита. - person user; 22.03.2012
comment
@MichaelKjörling один из 33 для 0 по умолчанию - person ratchet freak; 22.03.2012
comment
@MichaelKjörling: Честно говоря, всего 32 члена являются степенью двойки, поскольку 0 не является степенью двойки. Таким образом, 33 члена, каждый из которых является степенью двойки, не совсем правильно (если только вы не считаете 2 ** -infinity степенью двойки). - person Brian; 23.03.2012
comment
Если использовать побитовые операции, не ограничит ли это количество членов перечисления до 32, предполагая, что мы начинаем с 1? 32-й член будет иметь (двоичное) значение 1000000000000000000000000000000000. N членов будут равны N битам, или я ошибаюсь? - person Joel Peltonen; 08.08.2012
comment
@Nnotlep 32 позиции по 1 в каждой позиции, да, но 33-я - это все 0, а 1 никогда не появляется. ;-) - person Louis St-Amour; 19.04.2014

Поскольку эти значения представляют уникальные битовые позиции в двоичном формате:

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

и т. д., поэтому

1 | 2 == binary 00000011

РЕДАКТИРОВАТЬ:

3 == binary 00000011

3 в двоичном формате представлен значением 1 как в разряде единиц, так и в разряде двоек. На самом деле это то же самое, что и значение 1 | 2. Поэтому, когда вы пытаетесь использовать двоичные разряды в качестве флагов для представления некоторого состояния, 3 обычно не имеет смысла (если только нет логического значения, которое на самом деле является комбинацией двух)

Для дальнейшего разъяснения вы можете расширить свой пример перечисления следующим образом:

[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.

person Chris Shain    schedule 21.03.2012
comment
а в чем проблема с 2|3 ? - person Pascal; 21.03.2012
comment
@Pascal: Поскольку 3 является 11 двоичным, т. Е. Он не сопоставляется с одним установленным битом, поэтому вы теряете возможность сопоставлять 1 бит в произвольной позиции со значимым значением. - person Ed S.; 21.03.2012
comment
@Pascal, другими словами, 2|3 == 1|3 == 1|2 == 3. Таким образом, если у вас есть значение с двоичным кодом 00000011, а ваши флаги включают значения 1, 2 и 3, то вы не будете знать, представляет ли это значение 1 and 3, 2 and 3, 1 and 2 или only 3. Это делает его намного менее полезным. - person yshavit; 22.03.2012

[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
}

Я думаю, что такое письмо легче понять и прочитать, и вам не нужно его вычислять.

person Dozer    schedule 01.04.2012

Они используются для представления битовых флагов, которые позволяют комбинировать значения перечисления. Я думаю, будет понятнее, если вы запишете значения в шестнадцатеричном формате

[Flags]
public Enum Permissions
{
  None =  0x00,
  Read =  0x01,
  Write = 0x02,
  Delete= 0x04,
  Blah1 = 0x08,
  Blah2 = 0x10
}
person JaredPar    schedule 21.03.2012
comment
хорошо, int 16 более удобочитаем, чем шестнадцатеричный 0x10 для меня с int, я знаю, что это всегда двойное значение от 2 до 4, от 4 до 8, от 8 до 16, от 16 до 32 и т. д. с шестнадцатеричным я не знаю, может быть, я тоже не фирма с шестнадцатеричными значениями. - person Pascal; 21.03.2012
comment
@Pascal: Возможно, на данный момент это более читабельно для вас, но по мере того, как вы приобретаете опыт, просмотр байтов в шестнадцатеричном формате становится второй натурой. Две цифры в шестнадцатеричном формате соответствуют одному байту, который соответствует 8 битам (ну... в любом случае байт обычно равен 8 битам... не всегда так, но для этого примера можно обобщать). - person Ed S.; 21.03.2012
comment
@ Паскаль, быстро, что получится, если умножить 4194304 на 2? Как насчет 0x400000? Намного легче распознать 0x800000 как правильный ответ, чем 8388608, а также менее подвержено ошибкам при вводе шестнадцатеричного значения. - person phoog; 22.03.2012
comment
С первого взгляда намного проще сказать, правильно ли установлены ваши флаги (т. е. являются ли они степенью двойки), если вы используете шестнадцатеричный формат. Является ли 0x10000 степенью двойки? Да, он начинается с 1, 2, 4 или 8, а потом все 0. Вам не нужно мысленно переводить 0x10 в 16 (хотя это, вероятно, со временем станет второй натурой), просто подумайте об этом как о степени числа 2. - person Brian; 22.03.2012
comment
Я абсолютно согласен с Джаредом в том, что его гораздо легче записать в шестнадцатеричном формате. вы просто используете 1 2 4 8 и сдвиг - person bevacqua; 22.03.2012
comment
Лично я предпочитаю просто использовать, например. P_READ=1‹‹0, P_WRITE=1‹,1, P_RW = P_READ|P_WRITE. Я не уверен, работает ли такое свертывание констант в C#, но оно отлично работает в C/C++ (а также, я думаю, в Java). - person fluffy; 22.03.2012

Это больше похоже на комментарий, но поскольку это не поддерживает форматирование, я просто хотел включить метод, который я использовал для настройки перечислений флагов:

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

Я нахожу этот подход особенно полезным во время разработки в случае, когда вы хотите поддерживать свои флаги в алфавитном порядке. Если вы решите, что вам нужно добавить новое значение флага, вы можете просто вставить его в алфавитном порядке, и единственное значение, которое вам нужно изменить, — это то, которое ему сейчас предшествует.

Обратите внимание, однако, что после того, как решение опубликовано для любой производственной системы (особенно если перечисление доступно без жесткой связи, например, через веб-службу), настоятельно не рекомендуется изменять какое-либо существующее значение в перечислении.

person Mike Guthrie    schedule 27.03.2012

На этот вопрос есть много хороших ответов... Я просто скажу... если вам не нравится или не можете легко понять, что пытается выразить синтаксис <<.. Лично я предпочитаю альтернативу (и осмеливаюсь Я говорю, простой стиль объявления 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);

Журнал 513 == 513

Так легче (для меня, по крайней мере) понять. Выстраивайте из них… опишите желаемый результат, получите желаемый результат. Никаких «расчетов» не требуется.

person Alex Gray    schedule 25.04.2013