비트 연산을 하면 한 변수에 여러 옵션을 넣었다 뺐다 하는 효과를 줄 수 있습니다.
이 비트 연산을 열거형 맴버로 한다면 직관적으로 특정 옵션이 들어 있는지 확인하는 것이 가능하죠.
많은 언어가 열거형 맴버를 비트 연산에 활용할 수 있습니다.
닷넷(.Net)에서 어떻게 사용하는지 알아봅시다.
비트 연산의 원리를 간단하게 알아봅시다.
열거형 멤버의 비트 연산은 선언된 값의 합이 다른 멤버의 값과 절대로 겹치지 않는다는 것을 전제로 진행됩니다.
(참고 : MSDN - 열거형 디자인)
아래와 같이 열거형 멤버를 선언합니다.
/// <summary>
/// 권한1 옵션
/// </summary>
public enum Auth1Type
{
None = 0
, Opt0 = 1
, Opt1 = 2
, Opt2 = 4
, Opt3 = 8
, Opt4 = 16
, Opt5 = 32
, Opt6 = 64
, OptAll = int.MaxValue
}
위와 같이 선언하고 사용할 때는
Auth1Type type1Auth = Auth1Type.Opt1 | Auth1Type.Opt5;
이런 식으로 사용합니다.
위 식은 '34'의 값을 가지게 됩니다.
34 = Opt1 + Opt5
겹치는 멤버가 없죠,
이렇게 되면 항상 고유한 조합이 완성됩니다.
아래와 같이 손으로 계산해 봅시다.
6 = Opt1 + Opt2
14 = Opt1 + Opt2 + Opt3
30 = Opt1 + Opt2 + Opt3 + Opt4
이 결과를 직접 확인하기 위해 아래와 같이 열거형 멤버에 추가해 봅시다.
/// <summary>
/// 권한1 옵션
/// </summary>
public enum Auth1Type
{
None = 0
, Opt0 = 1
, Opt1 = 2
, Opt2 = 4
, Opt3 = 8
, Opt4 = 16
, Opt5 = 32
, Opt6 = 64
, Opt1_2 = 6
, Opt1_2_3 = 14
, Opt1_2_3_4 = 30
, Opt1_5 = 34
, OptAll = int.MaxValue
}
이제 'Opt1'과 'Opt5'를 합치면 'Opt1_5'가 나오게 됩니다.
우리는 10진수로 이것을 계산하고 있지만 내부적으로는 2진수로 계산됩니다.
위와 같이 수작업으로 2제곱을 하여 값을 할당하는 건 뒤로 갈수록 힘들어지고 가독성도 떨어집니다.
그래서 비트 연산자를 이용하여 선언해야 합니다.
(참고 : stackoverflow - What does the [Flags] Enum Attribute mean in C#?)
아래와 같이 선언합니다.
/// <summary>
/// 권한2 옵션
/// </summary>
public enum Auth2Type
{
None = 0
, Opt0 = 1 << 0
, Opt1 = 1 << 1
, Opt2 = 1 << 2
, Opt3 = 1 << 3
, Opt4 = 1 << 4
, Opt5 = 1 << 5
, Opt6 = 1 << 6
, Opt1_5 = Opt1 | Opt5
, OptAll = int.MaxValue
}
실행해보면 손으로 비트 계산해서 넣은 것과 같은 결과가 나옵니다.
스크린샷에는 문자열이 나오지 않는 걸로 보이는데..... 실제론 잘 나옵니다
비트 연산의 가장 큰 문제는 프로그래머가 별도의 처리를 하지 않으면 무슨 값이 들어가 있는지 알 수가 없다는 것입니다.
그래서 닷넷에는 플래그 속성(FlagsAttribute)이라는 것이 있습니다.
플래그 속성이 설정된 있는 열거형 멤버는 집합처리가 되어 선언되지 않은 값들도 자동으로 처리됩니다.
(참고 : MSDN - FlagsAttribute 클래스)
열거형 멤버 위에 '[Flags]'속성을 추가해 줍니다.
/// <summary>
/// 권한3 옵션
/// </summary>
[Flags]
public enum Auth3Type
{
None = 0
, Opt0 = 1 << 0
, Opt1 = 1 << 1
, Opt2 = 1 << 2
, Opt3 = 1 << 3
, Opt4 = 1 << 4
, Opt5 = 1 << 5
, Opt6 = 1 << 6
, OptAll = int.MaxValue
}
이제 사용해 봅시다.
합쳐진 옵션도 각각 표시되는 것을 알 수 있습니다.
이렇게 만든 플래그는 여러 연산자를 사용하여 사용할 수 있습니다.
(참고 : asp.net blog - Enum values as bit flags - using FlagsAttribute, 안드로메다 토끼 - [C#] Flag 연산 총정리)
//모든 값 빼기
typeAuth3 = Auth3Type.None;
//모든 값 넣기
typeAuth3 = Auth3Type.OptAll;
//값 넣기
typeAuth3 = Auth3Type.Opt1 | Auth3Type.Opt3;
//기존 값에 추가하기
typeAuth3 |= Auth3Type.Opt5 | Auth3Type.Opt6;
//값 빼기
typeAuth3 &= ~Auth3Type.Opt5;
//값 반전(있으면 빠지고 없으면 추가됨)
typeAuth3 ^= Auth3Type.Opt6;
//몇 가지 값을 빼고 전체 설정 넣기
typeAuth3 = Auth3Type.OptAll ^Auth3Type.Opt1 ^Auth3Type.Opt6;
//값 특정 값이 있는지 확인
typeAuth3.HasFlag(Auth3Type.Opt1);
'Enum.HasFlag'는 .net 4.0 이상에서만 동작합니다.
(참고 : MSDN - Enum.HasFlag(Enum) 메서드)
4.0 이하 버전에서는 직접 구현하여 사용해야 합니다.
완성된 샘플 : Github dang-gun - DotNetSamples/EnumBitFlags/
조심해야할 것은 32비트 int를 사용할때 플래그 범위는 0 ~ 31(0 ~ 2147483647)까지 입니다.
이런 거 잘되있는 맛에 닷넷 하는 거 아니겠습니까?
ㅎㅎㅎㅎㅎ