Что такое Enum с атрибутом [Flags] в C#?

Ответ

[Flags] — это атрибут в C#, который указывает, что перечисление (enum) должно интерпретироваться как битовая маска (флаги). Это позволяет комбинировать несколько значений перечисления в одной переменной с помощью побитовых операций, что удобно для представления множества состояний или опций.

Основные правила создания:

  1. Значения должны быть степенями двойки (1, 2, 4, 8...), чтобы каждый флаг занимал уникальный бит.
  2. Определите значение None = 0 для представления пустого набора флагов.
  3. Можно использовать удобную запись с левым сдвигом: Read = 1 << 0, Write = 1 << 1.

Пример объявления и использования:

[Flags]
public enum FileAccessPermissions
{
    None    = 0,          // 0b_0000
    Read    = 1 << 0,     // 0b_0001
    Write   = 1 << 1,     // 0b_0010
    Execute = 1 << 2,     // 0b_0100
    Delete  = 1 << 3,     // 0b_1000

    // Можно определять комбинированные значения для удобства
    ReadWrite = Read | Write // 0b_0011
}

// Использование: комбинация флагов с побитовым ИЛИ (|)
FileAccessPermissions userPermissions = FileAccessPermissions.Read | FileAccessPermissions.Write;

// Проверка наличия флага (рекомендуемый способ)
if (userPermissions.HasFlag(FileAccessPermissions.Write))
{
    Console.WriteLine("Разрешена запись.");
}

// Альтернативная проверка с побитовым И (&)
bool canReadAndWrite = (userPermissions & FileAccessPermissions.ReadWrite) == FileAccessPermissions.ReadWrite;

// Удаление флага с помощью побитового И с инверсией (& ~)
userPermissions = userPermissions & ~FileAccessPermissions.Write;

Важно: Атрибут [Flags] в основном влияет на поведение методов ToString() (который выведет "Read, Write") и Enum.Parse(). Сама битовая арифметика работает и без него, но его использование является хорошей практикой для самодокументируемости кода.

Ответ 18+ 🔞

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

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

Вот для этого и придумали флаги. По сути, это способ упаковать кучу true/false в одно число, используя биты. Каждый бит в числе — это одна твоя «лампочка».

Как это объявляется? Берёшь обычный enum и вешаешь на него волшебный атрибут [Flags]. Это как сказать компилятору: «Чувак, это не просто список, это битовая маска, не путай».

[Flags]
public enum FileAccessPermissions
{
    None    = 0,          // Всё выключено. 0b_0000
    Read    = 1 << 0,     // Включён самый правый бит. 0b_0001
    Write   = 1 << 1,     // Включён второй бит справа. 0b_0010
    Execute = 1 << 2,     // Третий бит. 0b_0100
    Delete  = 1 << 3,     // Четвёртый. 0b_1000

    // А это просто предустановка для ленивых, чтобы не писать Read | Write каждый раз
    ReadWrite = Read | Write // 0b_0011 (и Read, и Write)
}

Видишь эти 1 << 0? Это сдвиг единички влево. Так мы гарантируем, что каждое значение — это степень двойки, то есть в двоичном виде это одна единица и куча нулей. Это важно, чтобы биты не накладывались друг на друга и не создавали пиздец и неразбериху.

Как этим пользоваться? Очень просто.

  • Включить лампочки (добавить флаги): Используешь побитовое ИЛИ (|).

    // Даю пользователю права на чтение И запись
    var permissions = FileAccessPermissions.Read | FileAccessPermissions.Write;
    // В числах: 0b0001 | 0b0010 = 0b0011
  • Проверить, горит ли конкретная лампочка: Лучше всего через .HasFlag().

    if (permissions.HasFlag(FileAccessPermissions.Write))
    {
        Console.WriteLine("Можно писать, чувак!");
    }

    Или, для старых школотеров, через побитовое И (&):

    bool canWrite = (permissions & FileAccessPermissions.Write) == FileAccessPermissions.Write;
  • Выключить лампочку (убрать флаг): Используешь побитовое И (&) с инвертированным значением (~).

    // Отбираем право на запись
    permissions = permissions & ~FileAccessPermissions.Write;
    // Стало: 0b0011 & ~0b0010 = 0b0011 & 0b1101 = 0b0001 (осталось только Read)

А зачем вообще этот [Flags]? Без него всё тоже будет работать, битовые операции от этого не сломаются. Но это как ехать на машине с выключенными фарами ночью — можно, но ебанись об столб. Атрибут [Flags] — это твои фары:

  1. Для других разработчиков: Сразу ясно, что этот enum заточен под комбинации.
  2. Для .ToString(): Без атрибута permissions.ToString() вернёт загадочное число 3. С атрибутом — понятную строку "Read, Write". Удобно же для логов, ёпта!
  3. Для .Parse(): Можно будет написать Enum.Parse<FileAccessPermissions>("Read, Write") и получить правильную комбинацию.

Короче, если тебе нужно хранить набор независимых состояний «включено/выключено» — это твой инструмент. Главное, не забудь степени двойки для значений, а то получится ерунда, а не флаги.