Что означает атрибут [Flags] для перечислений (enum) в C#?
Что такое [Flags] и зачем он нужен?
Атрибут [Flags] в C# указывает, что перечисление (enum) предназначено для работы как битовая маска. Это позволяет хранить комбинацию нескольких значений в одной переменной. Без этого атрибута перечисление ведет себя как обычный набор констант, где каждое значение уникально и не может быть скомбинировано.
Почему значения должны быть степенями двойки?
Каждое значение флага представляет один бит в двоичном представлении числа. Это позволяет комбинировать флаги с помощью побитовых операций:
[Flags]
enum Permissions
{
None = 0, // 0000
Read = 1, // 0001
Write = 2, // 0010
Execute = 4 // 0100
// Следующее значение должно быть 8 (1000) и т.д.
}
Как комбинировать флаги?
Используйте побитовый оператор ИЛИ (|):
Permissions userPerms = Permissions.Read | Permissions.Write;
// userPerms теперь содержит 0011 (Read + Write)
Как проверять наличие флага?
Используйте побитовый оператор И (&):
if ((userPerms & Permissions.Read) == Permissions.Read)
{
Console.WriteLine("Пользователь может читать");
}
// Или с помощью метода HasFlag() (доступен с .NET 4.0):
if (userPerms.HasFlag(Permissions.Write))
{
Console.WriteLine("Пользователь может писать");
}
Как добавлять и удалять флаги?
// Добавить флаг
userPerms |= Permissions.Execute; // Теперь 0111
// Удалить флаг
userPerms &= ~Permissions.Write; // Теперь 0101 (Read + Execute)
Что будет, если убрать [Flags]?
Без атрибута [Flags] код будет работать, но потеряется читаемость. Например:
// Без [Flags]
enum PermissionsNoFlag { None=0, Read=1, Write=2, Execute=4 }
var perms = (PermissionsNoFlag)(1 | 2);
Console.WriteLine(perms); // Выведет "3", а не "Read, Write"
// С [Flags]
[Flags] enum PermissionsWithFlag { None=0, Read=1, Write=2, Execute=4 }
var perms2 = PermissionsWithFlag.Read | PermissionsWithFlag.Write;
Console.WriteLine(perms2); // Выведет "Read, Write"
Атрибут влияет на поведение методов ToString(), Enum.Parse() и сериализацию. Без него комбинация флагов отображается как число, а не как список имен.
Важные правила:
- Всегда задавайте значение
None = 0для пустой комбинации - Каждый следующий флаг — степень двойки (1, 2, 4, 8, 16...)
- Можно добавлять составные значения для частых комбинаций, например:
ReadWrite = Read | Write - Не используйте
[Flags]для перечислений, где значения не должны комбинироваться (например, дни недели)
Полный пример:
[Flags]
enum FileAccess
{
None = 0,
Read = 1,
Write = 2,
Delete = 4,
FullControl = Read | Write | Delete // 7
}
class Program
{
static void Main()
{
FileAccess access = FileAccess.Read | FileAccess.Write;
// Проверка
Console.WriteLine(access.HasFlag(FileAccess.Read)); // True
Console.WriteLine(access.HasFlag(FileAccess.Delete)); // False
// Вывод: "Read, Write"
Console.WriteLine(access.ToString());
// Разбор из строки
var parsed = Enum.Parse<FileAccess>("Read, Write");
Console.WriteLine(parsed); // Read, Write
}
}
<<Практический пример: управление правами доступа
Атрибут [Flags] особенно полезен в реальных приложениях, где нужно хранить множество булевых состояний в одной переменной. Рассмотрим пример системы управления доступом к документам:
[Flags]
enum DocumentPermissions
{
None = 0,
View = 1, // 0001
Edit = 2, // 0010
Comment = 4, // 0100
Share = 8, // 1000
// Составные роли
Reader = View | Comment, // 0101
Editor = View | Edit | Comment, // 0111
Admin = View | Edit | Comment | Share // 1111
}
class Document
{
public string Title { get; set; }
public DocumentPermissions Permissions { get; set; }
public bool CanEdit => Permissions.HasFlag(DocumentPermissions.Edit);
}
// Использование
var doc = new Document
{
Title = "Отчет",
Permissions = DocumentPermissions.Editor
};
Console.WriteLine(doc.CanEdit); // True
Console.WriteLine(doc.Permissions); // View, Edit, Comment
Преимущества: вместо хранения четырех отдельных булевых переменных (CanView, CanEdit, CanComment, CanShare) мы используем одно перечисление. Это экономит память и упрощает передачу прав доступа через параметры методов или API.
Важно: если вы не используете атрибут [Flags], метод ToString() для комбинации значений (например, View | Edit) вернет просто число 3 вместо осмысленного "View, Edit". В большинстве случаев это делает код менее читаемым при отладке и логировании.