Почему перечисления флагов обычно определяются с шестнадцатеричными значениями

123

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

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

Когда я объявляю перечисление, я обычно объявляю его так:

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

Есть ли причина или объяснение, почему некоторые люди предпочитают записывать значение в шестнадцатеричном, а не в десятичном формате? На мой взгляд, проще запутаться при использовании шестнадцатеричных значений и случайно написать Flag5 = 0x16вместо Flag5 = 0x10.

Ади Лестер
источник
4
Что может снизить вероятность того, что вы будете писать, 10а не 0x10если вы будете использовать десятичные числа? В частности, потому что мы имеем дело с двоичными числами, а шестнадцатеричный тривиально можно преобразовать в / из двоичного? 0x111переводить в голове гораздо менее неприятно, чем 273...
cHao
4
Жаль, что в C # нет синтаксиса, который явно не требует написания степени двойки.
Colonel Panic
Вы делаете здесь что-то бессмысленное. Смысл флагов в том, что они будут поразрядно объединены. Но побитовые комбинации не являются элементами типа. Значение Flag1 | Flag23, а 3 не соответствует ни одному значению домена MyEnum.
Kaz
Где ты это видишь? с отражателем?
giammin 06
@giammin Это общий вопрос, а не о конкретной реализации. Например, вы можете взять проекты с открытым исходным кодом или просто код, доступный в сети.
Ади Лестер

Ответы:

184

Обоснования могут быть разными, но я вижу преимущество в том, что шестнадцатеричное число напоминает вам: «Хорошо, мы больше не имеем дело с числами в произвольном изобретенном людьми мире десятичной системы. Мы имеем дело с битами - миром машин - и мы поиграем по ее правилам ". Шестнадцатеричный формат используется редко, если только вы не имеете дело с относительно низкоуровневыми темами, в которых структура памяти данных имеет значение. Его использование намекает на то, что мы сейчас в такой ситуации.

Кроме того, я не уверен насчет C #, но я знаю, что в C x << y- допустимая константа времени компиляции. Наиболее очевидным кажется использование битовых сдвигов:

[Flags]
public enum MyEnum
{
    None  = 0,
    Flag1 = 1 << 0,
    Flag2 = 1 << 1,
    Flag3 = 1 << 2,
    Flag4 = 1 << 3,
    Flag5 = 1 << 4
}
существует, FORALL
источник
7
Это очень интересно, и на самом деле это действительное перечисление C #.
Ади Лестер
13
+1 с этой нотацией вы никогда не ошибетесь при вычислении значений перечисления
Сергей Березовский
1
@AllonGuralnek: Назначает ли компилятор уникальные битовые позиции с учетом аннотации [Flags]? Обычно он начинается с 0 и идет с приращением 1, поэтому любое значение перечисления, присвоенное 3 (десятичное), будет равно 11 в двоичном формате, установив два бита.
Eric J.
2
@Eric: Ха, я не знаю почему, но я всегда был уверен, что он присваивает значения степени двойки. Я только что проверил и, думаю, ошибся.
Аллон Гуралнек 06
38
Еще один интересный факт с x << yобозначениями. 1 << 10 = KB, 1 << 20 = MB, 1 << 30 = GBИ так далее. Это действительно хорошо, если вы хотите создать массив размером 16 КБ для буфера, вы можете просто пойти,var buffer = new byte[16 << 10];
Скотт Чемберлен
43

Это позволяет легко увидеть, что это бинарные флаги.

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

Хотя прогрессия делает это еще яснее:

Flag6 = 0x20  // == 00100000
Flag7 = 0x40  // == 01000000
Flag8 = 0x80  // == 10000000
Одед
источник
3
На самом деле я добавляю 0 перед 0x1, 0x2, 0x4, 0x8 ... Итак, я получаю 0x01, 0x02, 0x04, 0x08 и 0x10 ... Мне так легче читать. Я что-то напортачил?
LightStriker
4
@Light - Вовсе нет. Это очень часто, поэтому вы можете увидеть, как они совпадают. Просто делает биты более явными :)
Одед
2
@LightStriker просто выбросит его, что имеет значение, если вы не используете шестнадцатеричный. Значения, начинающиеся только с нуля, интерпретируются как восьмеричные. Так и 012есть на самом деле 10.
Джонатон Рейнхарт
@JonathonRainhart: Это я знаю. Но я всегда использую шестнадцатеричный код при использовании битовых полей. Я не уверен, что буду чувствовать себя в безопасности intвместо этого. Я знаю, что это глупо ... но от привычек трудно избавиться.
LightStriker
36

Я думаю, это просто потому, что последовательность всегда 1, 2, 4, 8, а затем добавляется 0.
Как видите:

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

и так далее, если вы помните последовательность 1-2-4-8, вы можете построить все последующие флаги, не запоминая степени двойки.

VRonin
источник
13

Потому [Flags]что означает, что перечисление действительно является битовым полем . С [Flags]вы можете использовать побитовое И ( &) и OR ( |) операторы , чтобы объединить флаги. При работе с такими двоичными значениями почти всегда проще использовать шестнадцатеричные значения. Именно по этой причине мы в первую очередь используем шестнадцатеричные числа. Каждому шестнадцатеричному символу соответствует ровно один полубайт (четыре бита). С десятичным числом это преобразование 1 в 4 не выполняется.

Джонатон Рейнхарт
источник
2
На самом деле способность использовать побитовые операции не имеет ничего общего с атрибутом flags.
Маттиас Нордквист,
4

Потому что существует механический и простой способ удвоить степень двойки в шестнадцатеричной системе. В десятичном выражении это сложно. Это требует длительного умножения в голове. В шестнадцатеричном формате это простое изменение. Вы можете выполнять это полностью, 1UL << 63но не можете использовать десятичные дроби.

USR
источник
1
Я думаю, в этом есть смысл. Для перечислений с большим набором значений это самый простой способ (за возможным исключением примера @ Hypercube).
Ади Лестер
3

Потому что людям легче следить за тем, где биты находятся во флаге. Каждая шестнадцатеричная цифра может соответствовать 4-битной двоичной системе.

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

... and so on

0xF = 1111

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

Итак, если вам нужны 16-битные флаги, вы будете использовать 4-значные шестнадцатеричные значения, и таким образом вы сможете избежать ошибочных значений:

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
Только ты
источник
Это хорошее объяснение .... не хватает только двоичного эквивалента, показывающего окончательную последовательность битов, полубайтов и байтов;)
GoldBishop