2020. 2. 11. 15:30

비트 연산을 하면 한 변수에 여러 옵션을 넣었다 뺐다 하는 효과를 줄 수 있습니다.

이 비트 연산을 열거형 맴버로 한다면 직관적으로 특정 옵션이 들어 있는지 확인하는 것이 가능하죠.

 

많은 언어가 열거형 맴버를 비트 연산에 활용할 수 있습니다.

닷넷(.Net)에서 어떻게 사용하는지 알아봅시다.

 

연관글 영역

 

 

1. 원리 이해하기

비트 연산의 원리를 간단하게 알아봅시다.

열거형 멤버의 비트 연산은 선언된 값의 합이 다른 멤버의 값과 절대로 겹치지 않는다는 것을 전제로 진행됩니다.

(참고 : 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. 열거형 멤버를 비트로 선언하기

 

위와 같이 수작업으로 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
}

 

 

실행해보면 손으로 비트 계산해서 넣은 것과 같은 결과가 나옵니다.

 

 

스크린샷에는 문자열이 나오지 않는 걸로 보이는데..... 실제론 잘 나옵니다

 

 

 

3. 플래그 속성(FlagsAttribute) 사용하기

비트 연산의 가장 큰 문제는 프로그래머가 별도의 처리를 하지 않으면 무슨 값이 들어가 있는지 알 수가 없다는 것입니다.

그래서 닷넷에는 플래그 속성(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
}

 

이제 사용해 봅시다.

 

 

합쳐진 옵션도 각각 표시되는 것을 알 수 있습니다.

 

 

4. 활용하기

이렇게 만든 플래그는 여러 연산자를 사용하여 사용할 수 있습니다.

(참고 : 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)까지 입니다.

 

이런 거 잘되있는 맛에 닷넷 하는 거 아니겠습니까?

ㅎㅎㅎㅎㅎ