플래그 Enum은 다수의 옵션을 비트 단위로 조합해 표현하는 데 유용합니다. 성능과 메모리를 아끼면서 가독성도 유지할 수 있어 권한, 상태, 기능 토글 등에 많이 사용합니다. 핵심은 [Flags]와 2의 거듭제곱 값, 그리고 안전한 비트 연산 패턴입니다.
1. [Flags] 기본과 값 설계
각 항목은 2의 거듭제곱으로 지정하고, None=0을 반드시 포함합니다. All은 정의된 플래그들을 OR로 묶어 명시적으로 구성합니다.
using System;
[Flags]
public enum FilePermission : ushort
{
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
Delete = 1 << 3,
All = Read | Write | Execute | Delete
}
팁: All을 ~0으로 만들기보다 실제 정의된 플래그의 조합으로 두면 향후 검증과 유지보수가 쉬워집니다. 부호 문제를 피하려면 ushort, uint 같은 부호 없는 기반 형식을 고려합니다.
2. 자주 쓰는 연산 패턴(Set/Clear/Toggle/Check)
HasFlag는 간단하지만 상대적으로 느릴 수 있습니다. 확장 메서드로 자주 쓰는 연산을 묶어 두면 안전하고 빠릅니다.
public static class FilePermissionExtensions
{
public static bool IsAnySet(this FilePermission value, FilePermission flags)
=> (value & flags) != 0;
public static bool IsAllSet(this FilePermission value, FilePermission flags)
=> (value & flags) == flags;
public static FilePermission Set(this FilePermission value, FilePermission flags)
=> value | flags;
public static FilePermission Clear(this FilePermission value, FilePermission flags)
=> value & ~flags;
public static FilePermission Toggle(this FilePermission value, FilePermission flags)
=> value ^ flags;
public static bool IsValid(this FilePermission value)
=> (value & ~FilePermission.All) == 0;
public static FilePermission Sanitize(this FilePermission value)
=> value & FilePermission.All;
}
// 사용 예
var p = FilePermission.Read.Set(FilePermission.Write); // Read | Write
bool canRW = p.IsAllSet(FilePermission.Read | FilePermission.Write); // true
p = p.Clear(FilePermission.Execute); // Execute 제거
p = p.Toggle(FilePermission.Delete); // Delete 토글
3. HasFlag vs 비트 연산 성능
HasFlag는 가독성이 좋지만, (value & flag) != 0 같은 비트 연산이 일반적으로 더 빠릅니다. 성능이 중요한 경로에서는 비트 연산을 권장합니다.
bool ok1 = p.HasFlag(FilePermission.Read); // 간단하지만 느릴 수 있음
bool ok2 = (p & FilePermission.Read) != 0; // 권장: 박싱 없이 빠름
bool all = (p & (FilePermission.Read | FilePermission.Write))
== (FilePermission.Read | FilePermission.Write);
4. 유효성 검사와 안전한 병합
정의되지 않은 비트가 섞이지 않도록 검증과 정규화를 수행합니다.
static bool IsValid(FilePermission value)
=> (value & ~FilePermission.All) == 0;
static FilePermission Normalize(FilePermission value)
=> value & FilePermission.All;
입력 수신 시 IsValid로 검증하거나 Normalize로 정리하면 안정성이 올라갑니다.
5. 비트 필드로 값 압축하기(패킹/언패킹)
여러 작은 정수/불리언을 하나의 정수에 담아 저장/전송 비용을 절감하는 패턴입니다.
public static class PackedSettings
{
private const int LevelBits = 3; // 0..7
private const int ActiveBits = 1; // 0..1
private const int RegionBits = 4; // 0..15
private const int LevelShift = 0;
private const int ActiveShift = LevelShift + LevelBits; // 3
private const int RegionShift = ActiveShift + ActiveBits; // 4
private const int LevelMask = (1 << LevelBits) - 1; // 0b111
private const int ActiveMask = (1 << ActiveBits) - 1; // 0b1
private const int RegionMask = (1 << RegionBits) - 1; // 0b1111
public static int Pack(int level, bool active, int region)
{
level = level & LevelMask;
int a = active ? 1 : 0;
region = region & RegionMask;
return (level << LevelShift)
| (a << ActiveShift)
| (region << RegionShift);
}
public static int GetLevel(int value) => (value >> LevelShift) & LevelMask;
public static bool GetActive(int value) => ((value >> ActiveShift) & ActiveMask) != 0;
public static int GetRegion(int value) => (value >> RegionShift) & RegionMask;
}
// 사용 예
int packed = PackedSettings.Pack(5, true, 9);
int level = PackedSettings.GetLevel(packed); // 5
bool active = PackedSettings.GetActive(packed); // true
int region = PackedSettings.GetRegion(packed); // 9
6. 직렬화/DB 저장 팁
System.Text.Json에서 문자열로 플래그 이름을 쓰려면 컨버터를 추가합니다. 플래그 조합은 "Read, Write"처럼 직렬화됩니다. EF Core는 기본적으로 숫자로 저장합니다.
using System.Text.Json;
using System.Text.Json.Serialization;
[Flags]
public enum Permission { None = 0, Read = 1, Write = 2, Execute = 4 }
public record Role(string Name, Permission Permissions);
var role = new Role("dev", Permission.Read | Permission.Write);
var options = new JsonSerializerOptions { WriteIndented = true };
options.Converters.Add(new JsonStringEnumConverter());
string json = JsonSerializer.Serialize(role, options);
// { "Name": "dev", "Permissions": "Read, Write" }
// EF Core 예시
public class User
{
public int Id { get; set; }
public Permission Permissions { get; set; } // DB에 정수로 저장됨
}
7. 플래그가 맞지 않은 경우
상호 배타적인 상태(예: Draft/Published/Archived)는 일반 Enum이 적합합니다. 플래그는 여러 상태가 동시에 의미 있게 켜질 수 있을 때만 사용합니다.
8. 빠른 동작 확인
간단한 테스트로 동작을 점검합니다.
static void Main()
{
FilePermission p = FilePermission.Read | FilePermission.Write;
Console.WriteLine(p); // Read, Write
Console.WriteLine(p.IsAllSet(FilePermission.Read | FilePermission.Write)); // True
Console.WriteLine((p & FilePermission.Execute) != 0); // False
}
정리: 플래그 Enum은 올바른 값 설계, 안전한 비트 연산, 유효성 검사, 직렬화 전략을 갖추면 강력한 도구가 됩니다. 빈번한 경로에서는 HasFlag 대신 비트 연산을 사용하고, 비트 필드 패킹은 공간 효율이 중요할 때만 적용합니다.
'C#' 카테고리의 다른 글
| C# ThreadPool Worker Thread 관리 최적화 (0) | 2026.07.09 |
|---|---|
| C# DateTime.ParseExact와 TryParseExact 활용 (0) | 2026.07.08 |
| C# Dictionary의 ValueComparer 커스터마이징 (0) | 2026.07.08 |
| C# IDisposable 구현 시 Finalizer와의 상호작용 (0) | 2026.07.07 |
| C# Expression Tree로 동적 LINQ 쿼리 빌드하기 (1) | 2026.07.07 |