Почему перечисления [Flag] начинаются с 0 и увеличиваются на 1? - PullRequest
8 голосов
/ 24 июня 2010

Редактировать: Кажется, большинство людей неправильно поняли мой вопрос.

Я знаю, как работает enum, и я знаю бинарный файл. Мне интересно, , почему перечисления с атрибутом [Flags] созданы так, как они есть.

Оригинальный пост:

Это может быть дубликат, но я не нашел других сообщений, так что здесь.

Могу поспорить, что за этим стоит какое-то хорошее объяснение, я нахожу это немного подверженным ошибкам.

[Flag]
public enum Flagged
{
  One, // 0
  Two, // 1
  Three, // 2
  Four, // 3
}

Flagged f; // Defaults to Flagged.One = 0
f = Flagged.Four;
(f & Flagged.One) != 0; // Sure.. One defaults to 0
(f & Flagged.Two) != 0; // 3 & 1 == 1
(f & Flagged.Three) != 0; // 3 & 2 == 2

Разве не было бы больше смысла, если бы он сделал что-то подобное?

[Flag]
public enum Flagged
{
  One = 1 << 0, // 1
  Two = 1 << 1, // 2
  Three = 1 << 2, // 4
  Four = 1 << 3, // 8
}

Flagged f; // Defaults to 0
f = Flagged.Four;
(f & Flagged.One) != 0; // 8 & 1  == 0
(f & Flagged.Two) != 0; // 8 & 2 == 0
(f & Flagged.Three) != 0; // 8 & 4 == 0
(f & Flagged.Four) != 0; // 8 & 8 == 8

Конечно .. Я не совсем уверен, как он должен обрабатывать пользовательские флаги, подобные этому

[Flag]
public enum Flagged
{
   One, // 1
   Two, // 2
   LessThanThree = One | Two,
   Three, // 4? start from Two?
   LessThanFour = Three | LessThanThree,
   Three, // 8? start from Three?
}

Спецификация дает некоторые рекомендации

Определите константы перечисления в степенях двух, то есть 1, 2, 4, 8 и т. Д. Это означает, что отдельные флаги в комбинированных константах перечисления не перекрываются.

Но, возможно, это следует сделать автоматически, так как я уверен, что вы никогда не захотите, чтобы мой первый пример появился. Пожалуйста, просветите меня:)

Ответы [ 6 ]

12 голосов
/ 24 июня 2010

Атрибут Flags используется только для форматирования значений как нескольких значений.Битовые операции работают с базовым типом с атрибутом или без него.

7 голосов
/ 24 июня 2010

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

Кроме этого, вам и вашему проекту действительно решать, требуется ли вам нулевое значение или нет.

Поскольку перечисления флагов по-прежнему являются просто перечислениями (FlagsAttribute просто указывает отладчику интерпретировать значения как комбинации других значений), следующее значение в перечислении всегда на единицу больше, чем предыдущее значение. Следовательно, вы должны четко указывать битовые значения, так как вы можете выражать комбинации как побитовые ИЛИ других значений.

Тем не менее, весьма разумно представить синтаксис для перечислений флагов, который требует, чтобы все побитовые комбинации помещались в конце определения перечисления или были помечены каким-либо образом, чтобы компилятор знал, как обрабатывать все остальное.

Например (при условии ключевого слова flags и нахождении в северном полушарии),

flags enum MyFlags
{
   January,
   February,
   March,
   April,
   May,
   June,
   July,
   August,
   September,
   October,
   November,
   December,
   Winter = January | February | March
   Spring = April | May | June
   Summer = July | August | September
   Autumn = October | November | December
} 

С этим синтаксисом компилятор может сам создать значение 0 и автоматически присвоить флаги другим значениям.

6 голосов
/ 24 июня 2010

Атрибутом является [Флаги], а не [Флаг], и в этом нет ничего волшебного. Единственное, на что он влияет, это метод ToString. Когда указано [Flags], значения выходят через запятую. Вы должны указать значения, чтобы сделать его действительным для использования в битовом поле.

4 голосов
/ 24 июня 2010

Нет ничего в аннотированной спецификации C # 3.Я думаю, что может быть чем-то в аннотированной спецификации C # 4 - я не уверен.(Я думаю, что я начал писать такую ​​аннотацию самостоятельно, но затем удалил ее.)

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

[Flags]
enum Features
{
    Frobbing, // 1
    Blogging, // 2 
    Widgeting, // 4
    BloggingAndWidgeting = Frobbing | Blogging, // 6
    Mindnumbing // ?
}

Какое значение должен иметь Mindnumbing?Следующий бит, который не используется?А что если вы установите фиксированное целочисленное значение?

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

2 голосов
/ 24 июня 2010

Проще говоря, Flags - это атрибут. Он не применяется до тех пор, пока не будет создано перечисление, и, следовательно, не изменит значения, присвоенные перечислению.

Сказав это, на странице MSDN Проектирование перечислений флагов это говорит:

Используй силы двух для флагов значения перечисления, чтобы они могли быть свободно комбинируется с использованием побитового ИЛИ работа.

Важно: если вы не используете полномочия двух или комбинации степеней двух, побитовых операции не будут работать, как ожидалось.

Аналогично, на странице для FlagsAttribute написано

Определить константы перечисления в полномочиях из двух, то есть 1, 2, 4, 8, и так на. Это означает, что отдельные флаги в комбинированные константы перечисления не пересекаться.

0 голосов
/ 24 июня 2010

В C возможно (ab) использовать препроцессор для автоматической генерации перечислений степени двух.Если у вас есть макрос make_things, который расширяется до «make_thing (flag1) make_thing (flag2) make_thing (flag3)» и т. Д., То можно вызывать этот макрос несколько раз с различными определениями make_thing, чтобы получить степень двойкипоследовательность имен флагов, а также некоторые другие полезности.

Например, начните с определения make_thing (x) как "LINEAR_ENUM _ ## x" (включая запятую), а затем используйте оператор enum для генерациисписок перечислений (в том числе вне макроса make_things, LINEAR_NUM_ITEMS).Затем создайте другое перечисление, на этот раз с make_thing (x), определенным как "FLAG_ENUM _ ## x = 1 <<p> Некоторые из возможных вещей, которые можно сделать таким образом, с автоматически синхронизированными флаговыми и линейными значениями; код можетделать хорошие вещи, такие как "if (thingie [LINEAR_ENUM_foo] thing_errors | = FLAG_ENUM_foo;" (используя как линейные значения, так и значения флагов). К сожалению, я не знаю ни одного способа сделать что-либо подобное в C # или VB.net.

...