Перечисления как битовые флаги

Перечисления можно использовать для хранения битовых флагов, благодаря этому экземпляр enum может содержать в себе комбинацию значений констант, определенных в списке. Для создания перечисления с битовыми флагами, используется атрибут [Flags]. Значения констант задаются таким образом, чтобы к ним можно было применять битовые операции.

Для использования перечислений как битовых флагов необходимо соблюдать требования:

  • Обязательно включайте в перечисление константу со значением 0, обычно используется имя None;
  • Константы должны быть степенями двойки 1, 2, 4… Это гарантирует что комбинации флагов не будут перекрываться;
  • Если сочетания флагов часто используются, добавьте их в перечисление;
  • Для констант используйте только положительные значения, поскольку отрицательные могут внести неоднозначность.

Определение битовых флагов перечислений

Поскольку константы должны содержать степени двойки, можно задавать их несколькими вариантами:

  • Двоичными литералами;
  • Числовым литералом с операцией сдвига;
  • Числами десятичной системы;
  • Литералами шестнадцатеричной системы.

Для присвоения значений константам, проще и нагляднее использовать двоичные литералы:

[Flags]
enum AccessType
{
    Deny      = 0b00,
    ReadOnly  = 0b01,
    Write     = 0b10,
    ReadWrite = 0b11 //часто используемая комбинация флагов
}

или операцию сдвига:

[Flags]
enum Border
{
    None   = 0,
    Left   = 1 << 0, //1
    Top    = 1 << 1, //2
    Right  = 1 << 2, //4
    Bottom = 1 << 3  //8
}

Битовые операции

Для комбинирования, сброса и проверки флагов используются битовые операции.

Рассмотрим пример перечисления которое описывает дни недели, значения представим в виде двоичных литералов(можно использовать любую из доступных систем счисления). С помощью битовых операций мы можем ввести переменные которые будут обозначать выходные и рабочие дни недели.

using System;
class Program
{
    [Flags]
    enum Days
    {
        Not = 0b00000000,
        Mon = 0b00000001,
        Tue = 0b00000010,
        Wed = 0b00000100,
        Thu = 0b00001000,
        Fri = 0b00010000,
        Sat = 0b00100000,
        Sun = 0b01000000,
        //флаги можно комбинировать создавая наборы с помощью констант
        WorkingDays = Mon | Tue | Wed | Thu | Fri, //0b00011111
        //или используя значения
        Weekend = 0b01100000 //Sat|Sun
    }


    static void Main(string[] args)
    {
        var monday = Days.Mon;
        //проверка битового флага
        if ((monday & Days.WorkingDays) != 0)
        {
            Console.WriteLine("Понедельник - рабочий день");
        }

        var goodDays = Days.Thu | Days.Tue;
        var badDays = Days.Fri | Days.Mon;
        Console.WriteLine($"Хорошие дни: {goodDays}");
        Console.WriteLine($"Плохие дни: {badDays}");
        Console.ReadLine();
    }
}

Как видно из примера для установки флага используется оператор |(побитовое или):

var x = Days.Mon | Days.Tue;
x |= Days.Fri;
Console.WriteLine(x);

Для проверки флага используется конструкция с оператором &(побитовое и):

bool flag1 = (x & Days.Fri) == Days.Fri; //true
//или
bool flag2 = (x & Days.Fri) != 0; //true

Для сброса флага можно использовать оператор ^(xor):

x = x ^ Days.Tue;
Console.WriteLine(x);

Битовые маски широко используются в среде .Net, в частности для указания привязки объектов к контейнерам(Anchor) и определения уровня доступа к файлам(FileAccess), это лишь малая часть из перечислений которые входят в состав .Net Framework.

Смотрите также: