강태공97 2014. 1. 17. 21:39

발췌: http://zmeun.tistory.com/137

Enum과 bit 연산

C# 2.0 이하 2009/05/27 23:52

Enum bit 연산

종종 습관적으로, 또는 별 생각 없이 개발을 하는 경우가 있습니다. 제가 바로 그렇습니다. 쿨럭 -_-; 뭐 충분히 그럴 수 있습니다~~. 그렇죠? ^^; 그런데 이런 와중에 잊어버리는 것들이 있습니다. 그리고 그것으로 인해, 생각지 못한 버그가 발생하고.. 또 스스로 자학하고.. .. 이런 별로 상큼하지 못한 일들이 꼬리에 꼬리를 물죠.(세상 사는 게 참…)

이번에는 이런 상황 중에 해당 될 수 있는 내용에 대해 이야기 해 볼까 합니다. 저는 평소에 개발 할 때 enum 타입을 가끔 사용하곤 합니다. 어떤 값들을 구분해서 사용해야 할 때, 그 값들을 명료하게 구분할 수 있고 또 코드를 보기에도 쉬운 장점들이 있죠.

그렇다면 평소에 흔히 사용하는 형태의 아주 간단한 enum 예제 코드를 작성해 보겠습니다.

static void Main(string[] args)

{

CustomerOrder(Beverage.Coffee);

Console.Read();

}

public static void CustomerOrder(Beverage beverage)

{

Console.WriteLine("Customer Ordered Below Menu...");

if (beverage == Beverage.Coffee)

{

Console.WriteLine("Coffee");

}

if (beverage == Beverage.Tea)

{

Console.WriteLine("Tea");

}

if (beverage == Beverage.Juice)

{

Console.WriteLine("Juice");

}

}

public enum Beverage

{

Coffee = 0x1,

Tea,

Juice

}

얼핏 보기에도 아주 편안해 보이는 코드입니다. 흠잡을 때 없이 아주 훌륭하죠? ㅋㅋ Beverage라는 enum 타입을 정의해 놓고, CustomerOrder 메서드에 이 enum 타입을 파라미터로 전달해 해당하는 값을 출력하는 코드입니다.

그런데, 여기에 상황을 조금만 바꿔보죠. CustomerOrder에 전달하는 Beverage라는 enum 타입의 멤버를 하나가 아닌 두 개 이상 전달해야 한다면 어떻게 할까요? ~ 그러니까..다음과 같은 코드가 필요한 경우겠죠.

static void Main(string[] args)

{

CustomerOrder(Beverage.Coffee | Beverage. Tea);

Console.Read();

}

여러분들은 이와 같은 상황에서 어떻게 하시겠습니까? 혹시 오해하시는 분들이 계실지 몰라 말씀 드리면.. 이 코드는 오류가 발생하지 않습니다. 단지.. 이상한 결과가 나올 뿐이지요..

?? 이상한 결과? 그게 뭔데~ 라고 생각하시는 분들분명 계십니다. .. 그런 분들을 위해서.. 결과 화면을 먼저 보여 드리겠습니다.



!!~~ 웅성 웅성.. 하는 소리가 들립니다. ㅋㅋ 물론 뻔한 걸 물어 보는군.. 하는 분들도 많이 계실 것이구요. 분명 코드에서는 Coffee Tea를 주문 했는데.. 막상 나온 건 Juice입니다. 난 시키지도 않았는데 말이죠...그쵸? 이럴 땐 음료를 주문한 손님이나 이 코드를 작성한 개발자나 화나는 건 매 한가지입니다. -_-;;

 

이런 결과가 나오는 이유는 바로 bit 연산 때문입니다. 그래서 오늘은 옛 기억을 되새겨보는 차원에서 bit연산에 대해서 한번 회상해 볼까 합니다.

Bit 연산은 대략 |(or), &(and), ~(not) 이런 연산을 수행하며 각각의 연산은 다음과 같이 처리 됩니다.

x y |

------------------

0 0 0

0 1 1

1 0 1

1 1 1

x y &

------------------

0 0 0

0 1 0

1 0 0

1 1 1

x ~

------------------

0 1

1 0

이것을 앞에서 작성한 코드에 적용해 보면, 결과가 왜 그렇게 나왔는지 바로!! 그 즉시!! 당장!! 이해할 수 있습니다. ~ 한번 보시죠. Beverage라는 enum 타입은 1부터 시작합니다. 그렇게 되면, 그 다음 enum 타입의 멤버는 별도의 다른 상수 값을 지정하지 않을 경우 자연히 1씩 증가한 값을 갖게 되죠.

그럼 Coffee 1, Tea 2가 될 것이고, 이것을 다시 main 메서드 내에서 CustomerOrder 메서드에 파라미터로 전달 할 때 |(or) 연산을 통해서 전달합니다. 그럼 CustomerOrder 메서드에 전달된 Beverage enum 타입의 파라미터는 다음과 같은 bit 연산 과정을 거쳐 결과 값을 갖게 됩니다.


0 1 --> Coffee : 1

1 0 --> Tea : 2

-------

1 1 => Juice : 3

bit 연산에서 볼 수 있듯이 Coffee Tea |(or) 연산 결과는 3이 되고, 3 Beverage enum 타입의 멤버 중 Juice가 갖는 값이 됩니다. 그렇기 때문에 CustomerOrder 메서드의 실행 결과는 Juice가 출력되는 것입니다. (이 시점에서 ~ !!”.. 나오시죠? ^^)

! 그럼 이제 왜 그렇게 되는가는 이해했으니, 그럼 남은 건 어떻게 해야 하는 것인가!.. 겠죠.

그러기 위해서는 Beverage enum 타입 멤버가 갖는 상수의 값을 각각 지정해야 합니다. 왜냐하면 1씩 자동증가 하는 값을 enum의 멤버들이 갖게 되면, 앞으로 발생하게 되는 | 또는 &, ~ 연산이 앞의 예제 코드처럼 의도하지 않은 연산으로 인해 잘못 된 값이 나타날 수 있기 때문입니다. 그래서 저는 이렇게 한번 해 보았습니다. 먼저 각각의 enum 멤버의 값을 겹치지 않는 상수 값으로 초기화 합니다.

public enum Beverage : short

{

Coffee = 0x1,

Tea = 0x2,

Juice = 0x4

}

이렇게 해 두면 Coffee Tea |(or) 연산해도 Juice가 나올 수 없겠죠.(1 | 2하면 3이 되는데, Juice 4의 값을 갖기 때문이죠.)

그 다음에 CustomerOrder 메서드를 약간 수정합니다. 이렇게요…..

public static void CustomerOrder(Beverage beverage)

{

Console.WriteLine("Customer Ordered Below Menu...");

if ((Beverage.Coffee & beverage) == Beverage.Coffee)

{

Console.WriteLine("Coffee!");

}

if ((Beverage.Tea & beverage) == Beverage.Tea)

{

Console.WriteLine("Tea!");

}

if ((Beverage.Juice & beverage) == Beverage.Juice)

{

Console.WriteLine("Juice!");

}

}

if문의 조건 식이 조금 어색하죠? 그럼 왜 그런지 직접 bit 값으로 풀어서 한번 살펴 보겠습니다.

if ((Beverage.Coffee & beverage) == Beverage.Coffee)

1) Beverage.Coffee : 0 0 1

2) beverage : 0 0 1(Coffee) | 0 1 0(Tea) = 0 1 1

3) 0 0 1 & 0 1 1 = 0 0 1 => Coffee

1)번의 Coffeebit 값으로 001이고, 2) beverage Coffee Tea |(or) 연산이기 때문에 이것을 풀어보면 011이 됩니다. 그리고 1)번과 2)번을 다시 &(and) 연산하면 001이 되죠. 그럼 Beverge.Coffee와 같은 값이 되니.. if문의 조건 식은 true가 되어 if문의 블록에 있는 코드는 실행됩니다.

그리고 그 다음 if문도 이와 같은 식으로 한번 풀어 보겠습니다.

if ((Beverage.Tea & beverage) == Beverage.Tea)

1) Beverage.Tea : 0 1 0

2) beverage : 0 0 1(Coffee) | 0 1 0(Tea) = 0 1 1

3) 0 1 0 & 0 1 1 = 0 1 0 => Tea

역시 마찬가지로 1)번은 Beverage.Tea 2의 값을 갖고 있기 때문에, 이를 bit 값으로 풀면 010이 되고 2)번은 위와 마찬가지이므로 1) 2) &(and) 연산하면 결과는 010 Beverage.Tea가 됩니다. 이해 되시죠? (어째 산수 문제 푸는 것 같은..느낌이.. ^^;)

이런 식으로 하면 나머지 하나의 if문도 이해하실 수 있을 겁니다. 그리고 이렇게 작성한 코드를 실행하면 예상과 같이 다음과 같은 결과를 확인할 수 있습니다.


 

이제서야 원하는 결과가 나왔죠? 냐햐햐.. 살짝 보람이 있습니다. ~ 그렇다면여기에서 간단히, 황을 한번 더 살짝 꼬아 보겠습니다. 어떤 상황에서는 앞에서 정의한 enum의 멤버를 모두 체크해야 할 경우도 있을 수 있습니다. 그렇다면 모든 enum 타입의 멤버를 |(or) 연산으로 모두 전달해야 할까요? 그것도 방법이죠. 하지만, 그보다 더 간단한 방법이 있습니다. 바로 이렇게 하는 거죠.

public enum Beverage : short

{

Coffee = 0x1,

Tea = 0x2,

Juice = 0x4,

All = Coffee | Tea | Juice

}

static void Main(string[] args)

{

CustomerOrder(Beverage.All);

Console.Read();

}

어떠세요? ~~ 쉽죠잉~ ㅋㅋㅋ 이렇게 간단히 코드 수정을 통해서 다음과 같은 결과를 얻을 수 있습니다.



간만에 생각해 보는 bit 연산이었습니다. 옛 기억이 새록새록 나시죠? 물론 이런 것이 습관이 되신 분들은 ~ 이런걸 이야기 하냐..”라고 생각하실 수 있겠지만, 사실 요즘은 개발 환경이 너무 좋아서 이런 거 잘 생각 못하고 지내는 일들이 다분히 있습니다. 저 역시 마찬가지 구요. 그래서 한번 remind 차원에서 정리해 보았습니다. 그리고 역시나 글도 길어 졌네요. ^^; 제 경험상으로도 긴 글들은 역시 읽기에 너무 부담이 되죠.. 글은 짧게. 내용은 간단히.. 이런 게 생활화 되야 하는데.. 저는 언제나. 그리 될는지.. ~… -_-;

 

그럼 오늘은 여기까지 해서 마치도록 하겠습니다. 긴 장문 읽으시느라 고생 하셨습니다. 그럼 즐거운 하루 되시기 바랍니다. 감사합니다. ^o^v