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

159

Почему люди всегда используют значения перечисления как, 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: Вам может быть полезно прочитать о побитовом ИЛИпобитовом И ), что и |&) представляет. Различные ответы предполагают, что вы знакомы с этим.
Брайан
2
@IAdapter Я понимаю, почему вы так думаете, поскольку ответы на оба вопроса одинаковы, но я думаю, что вопросы разные. Другой вопрос просто просит пример или объяснение атрибута Flags в C #. Этот вопрос, кажется, касается концепции битовых флагов и основополагающих принципов.
Джереми С.

Ответы:

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, и когда вы не хотите применять Flags также к некоторым перечислениям.
Энди
3
@ Энди: На самом деле, Flagsатрибут делает немного больше, чем дает вам «красивую печать» iirc. Вы можете использовать перечисляемое значение в качестве флага независимо от наличия атрибута.
Эд С.
3
@detly: потому что операторы if в C # требуют логического выражения. 0нет false; falseесть false. Вы могли бы, однако, написать if((permissions & Permissions.Write) > 0).
Эд С.
2
Вместо «хитрого» (permissions & Permissions.Write) == Permissions.Writeтеперь вы можете использоватьenum.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 обычно не имеет смысла (если нет логического значения, которое на самом деле является комбинацией двух)

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

[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
}
JaredPar
источник
4
@Pascal: Возможно, в данный момент он более удобен для чтения, но по мере приобретения опыта просмотр байтов в шестнадцатеричном формате становится второй натурой. Две цифры в шестнадцатеричном отображении соответствуют одному байту и соответствуют 8 битам (ну ... байт обычно равен 8 битам в любом случае ... не всегда верно, но для этого примера можно обобщить).
Эд С.
5
@ Паскаль быстро, что ты получаешь, когда умножаешь 4194304на 2? Как насчет 0x400000? Гораздо легче распознать 0x800000правильный ответ, чем 8388608, а также менее подвержен ошибкам при вводе шестнадцатеричного значения.
Фог
6
С первого взгляда гораздо проще определить, правильно ли установлены ваши флаги (т. Е. Имеют степени 2), если вы используете гекс. Это 0x10000сила двух? Да, он начинается с 1, 2, 4 или 8 и имеет все 0 после. Вам не нужно мысленно переводить 0x10 в 16 (хотя в конечном итоге это, вероятно, станет второй натурой), просто подумайте об этом, как о «некоторой степени 2».
Брайан
1
Я абсолютно согласен с Джаредом в том, что в шестнадцатеричном коде гораздо проще записать. Вы просто используете 1 2 4 8 и сдвиг
bevacqua
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

Гораздо проще (по крайней мере, для себя) понять. Выстроите их в ряд ... опишите результат, который вы хотите, получите результат, который вы ХОТИТЕ. Никаких "вычислений" не требуется.

Алекс Грей
источник