Секрет [Flags] в C#: как написать код, который поймут даже джуны

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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

Если вы когда-нибудь писали на C# и видели в чужом коде странный атрибут [Flags] перед enum, то, скорее всего, задавались вопросом: "А что, если я его не поставлю — код перестанет работать?"

Спойлер: работать не перестанет. Но ваш код станет похож на велосипед с квадратными колёсами. Сегодня мы разберём, что означает атрибут [Flags] в C#, как его правильно использовать в 2025 году и почему без него ваши enum — это просто числа, а не осмысленные комбинации.

Тема взята из реального вопроса на Stack Overflow, который собрал тысячи просмотров. И это не случайно: правильное использование битовых флагов — один из тех навыков, который отличает разработчика, понимающего внутренности языка, от того, кто просто "копипастит с MSDN".

Что такое [Flags] в C# на самом деле?

Атрибут [Flags] — это не магический переключатель, который меняет поведение компилятора. Это метка для разработчика и для инструментов (например, для отладчика Visual Studio или консольного вывода).

Когда вы пишете:

[Flags]
public enum FilePermissions
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    Delete = 8
}

Вы говорите: "Эй, это перечисление — не про выбор одного варианта, а про комбинацию нескольких". Без атрибута [Flags] C# не знает, что вы собираетесь делать битовые операции. Но технически — можете.

Как работает битовая магия

Значения 1, 2, 4, 8 выбраны не случайно. В двоичной системе это:

  • 1 = 0001
  • 2 = 0010
  • 4 = 0100
  • 8 = 1000

Каждый флаг занимает свой бит. Поэтому вы можете скомбинировать Read и Write как 1 | 2 = 3 (0011). И система поймёт: "О, включены оба флага".

Если бы вы выбрали значения 1, 2, 3, 4 — комбинация 1 | 2 = 3 была бы неотличима от значения Execute = 3. Поэтому битовые флаги требуют степеней двойки.

Зачем нужен [Flags], если можно просто ставить числа?

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

  • Читаемость кода: permissions.HasFlag(FilePermissions.Write) понятнее, чем (permissions & 2) != 0.
  • Отладка: Visual Studio покажет Read, Write вместо числа 3.
  • ToString(): вызов myFlags.ToString() выдаст "Read, Write" вместо "3".
  • Самодокументирование: любой разработчик, увидев [Flags], сразу поймёт намерение.

Без атрибута [Flags] в C# вы теряете все эти бонусы. Код работает, но выглядит как "я написал это в 2 часа ночи перед дедлайном".

Лучшие практики работы с [Flags] в 2025 году

1. Всегда используйте степени двойки

Это правило номер один. Значения 1, 2, 4, 8, 16, 32... — и никак иначе. Для удобства можно использовать шестнадцатеричную запись:

[Flags]
public enum DayOfWeek
{
    None    = 0,
    Monday  = 0b_0000_0001,  // 1
    Tuesday = 0b_0000_0010,  // 2
    Wednesday = 0b_0000_0100, // 4
    Thursday = 0b_0000_1000, // 8
    Friday  = 0b_0001_0000,  // 16
    Weekend = Monday | Tuesday | Wednesday | Thursday | Friday
}

Обратите внимание: всегда включайте None = 0. Без него ваш enum по умолчанию будет равен 0, что может означать "ничего не выбрано".

2. Используйте Enum.HasFlag() — но с осторожностью

Метод HasFlag() появился в .NET 4.0 и стал спасением для новичков. Однако в 2025 году есть нюанс:

// Хорошо для читаемости:
if (permissions.HasFlag(FilePermissions.Read)) { ... }

// Но быстрее (особенно в горячих циклах):
if ((permissions & FilePermissions.Read) == FilePermissions.Read) { ... }

Когда использовать HasFlag: в обычном коде, где читаемость важнее микрооптимизации.

Когда избегать: в критических по производительности участках (игровые циклы, обработка больших массивов).

3. Комбинируйте флаги с умом

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

[Flags]
public enum UserRole
{
    None     = 0,
    View     = 1,
    Edit     = 2,
    Delete   = 4,
    Admin    = View | Edit | Delete,  // 7
    SuperAdmin = Admin | 8            // если добавить ещё флаг
}

Такой подход делает код самодокументированным. Вместо View | Edit | Delete вы пишете просто UserRole.Admin.

4. Не превышайте 32 флага для int и 64 для long

По умолчанию enum в C# использует int (32 бита). Если вам нужно больше — укажите long:

[Flags]
public enum HugeFlags : long
{
    Flag1 = 1L,
    Flag2 = 1L << 1,
    // ... до 64 флагов
    Flag64 = 1L << 63
}

Но помните: больше 64 флагов — повод задуматься о дизайне. Возможно, вам нужен HashSet<Enum> или BitArray.

Антипаттерны: чего делать НЕ стоит

❌ Использовать последовательные значения (1, 2, 3, 4)

Блоги

Рекомендаций по книгам