입력에 따라 분기를 나누어 처리할 때 분기마다 번호를 부여해주는 방법을 주로 사용했다. 예를 들어 체스 프로그램을 만든다고 하면 백은 양수, 흑은 음수로 킹은 1, 퀸은 2, 비숍은 3, 나이트는 4, 룩은 5, 폰은 6 이런식으로. 체스 정도면 어느정도 가독성을 유지할 수 있겠지만 만약 더 다양한 분기를 나누는 경우엔 코드를 한참 짜다 돌아봤을 때 1번이 뭔지, 5번이 뭔지, 27번이 뭐였는지 알아보기 힘들 수 있다. 이런 불편함을 막고 개발자의 편의를 보장하기 위해 enum 자료형이 있다. 열거형 자료는 enum 키워드로 선언하고 항상 정수 기반이기 때문에 사용할 수 있다. 그럼 체스 기물들은 이런 식으로 표현할 수 있다.
using System;
class Program
{
enum Pieces : Int32
{ // enum 이름 : 자료형 { 데이터 } 로 선언한다.
W_KING, // 자료형을 생략할 경우 Int32 자료형을 기반으로 한다.
W_QUEEN, // Pieces.W_QUEEN == 1
W_BISHOP, // Pieces.W_BISHOP == 2
W_KNIGHT, // Pieces.W_KNIGHT == 3
W_ROOK, // Pieces.W_ROOK == 4
W_PAWN, // Pieces.W_PAWN == 5
B_KING = 10,// 번호를 지정할 수도 있다.
B_QUEEN, // 다음 자료는 다음 번호로 지정된다.
B_BISHOP, // Pieces.B_BISHOP == 12
B_KNIGHT, // Pieces.B_KNIGHT == 13
B_ROOK, // Pieces.B_ROOK == 14
B_PAWN // Pieces.B_PAWN == 15
// enum 을 선언할 때 한 줄로 쭉 나열하는 경우도 있지만
// 한눈에 쉽게 알아볼 수 있도록 이렇게 행을 구분해주는 것이 좋다고 한다.
}
static void Main()
{
Pieces[,] Board = new Pieces[8, 8];
// 정수 기반 자료형이기 때문에 배열로 만들 수 있다.
Board[7, 0] = Pieces.W_ROOK;
if (Board[7, 0] == Pieces.W_ROOK)
Console.WriteLine("White Rook is on a8");
else
Console.WriteLine("White Rook is not on a8");
}
}
열거형은 이렇게 내가 값을 지정하지 않아도 컴파일러가 알아서 값을 지정해준다. 그것도 중복되지 않도록(기반 자료형의 범위를 넘어가지 않는 선에서). 값 지정의 목적이 값의 크기에 있는 것이 아니라 그냥 이름을 부여하는 것이기 때문에 번호를 어떻게 주던, 선언할 때 자료의 순서가 어떻게 되던 크게 상관은 없다.
이렇게 개체들을 관리하는데 도움을 주기도 하지만 Flags Attribute 클래스를 이용하면 좀 더 특별한 열거형을 만들 수 있다. 어떤 개체에 속성들을 부여할 수 있다. 이 방법을 이용하면 bool 변수 배열을 사용하는 것보다 더 알아보기 쉬운 코드를 만들 수 있다. 이게 이렇게 활용하능할지 잘 모르겠지만 위에서 체스의 예를 들었으니 기물의 진행방향에 대한 속성을 enum 으로 만들어보면 이렇다.
using System;
class Program
{
[Flags]
enum MoveStyle : byte
{
UDRL = 1, // 상하좌우 0000 0001
DIAGONALLY = 1 << 1,// 대각선 0000 0010
NEARAWAY = 1 << 2, // 1칸 이동 0000 0100
FARAWAY = 1 << 3, // 자유이동 0000 1000
KNIGHT = 1 << 4, // 나이트 이동 0001 0000
FORWARD = 1 << 5, // 앞으로 이동 0010 0000
JUMPOVER = 1 << 6 // 뛰어넘기 0100 0000
// 비트 연산을 이용하는 것이 코드를 간략화 하기에 좋다.
// byte 자료형을 사용했기 때문에 최대 8개의 요소를 표현할 수 있다.
// 복합적으로 자주 쓰이는 속성은 미리 섞어둘 수도 있다.
// 만약 어떤 게임에서 기물들의 이동방법이 단방향, 앞뒤, 4방향이라면
// 앞으로 이동에 0000 0001, 뒤로 이동에 0000 0010,
// 오른쪽 이동에 0000 0100, 왼쪽 이동에 0000 1000,
// 앞뒤 이동에 0000 0011, 좌우 이동에 0000 1100,
// 4방향 이동에 0000 1111 을 지정해둘 수 있다.
// 이러면 따로 속성을 조합하지 않아도 프리셋처럼 불러올 수 있다.
}
static void Main()
{
var King = MoveStyle.UDRL | MoveStyle.DIAGONALLY | MoveStyle.NEARAWAY;
// 킹은 상하좌우, 대각선으로 한 칸 이동, 세 가지 속성을 갖는다.
var Queen = MoveStyle.UDRL | MoveStyle.DIAGONALLY | MoveStyle.FARAWAY;
// 퀸은 상하좌우, 대각선으로 자유이동, 세 가지 속성을 갖는다.
var Bishop = MoveStyle.DIAGONALLY | MoveStyle.FARAWAY;
// 비숍은 대각선으로 자유이동, 두 가지 속성을 갖는다.
var Knight = MoveStyle.KNIGHT | MoveStyle.JUMPOVER;
// 나이트는 나이트스타일, 뛰어넘기, 두 가지 속성을 갖는다.
var Rook = MoveStyle.UDRL | MoveStyle.FARAWAY;
// 룩은 상하좌우로 자유이동, 두 가지 속성을 갖는다.
var Pawn = MoveStyle.FORWARD | MoveStyle.NEARAWAY;
// 폰은 앞으로 한 칸 이동, 두 가지 속성을 갖는다.
}
}
Flags Attribute 클래스에서는 각 요소의 합으로 다른 요소의 값을 정한다. 그래서 내가 값을 지정하지 않고 순차적으로 선언하게 되면 3이 할당되는 값은 1 + 2 로 지정된다. 그러니까 3은 1과 구분할 수 없게 된다. 이걸 막기위해 비트 연산을 활용한다. 0, 1, 2, 4, 8, ... 이렇게 순차적으로 부여하면 각 값이 섞여도 다른 자리의 비트를 갖게되기 때문에 서로 간섭받지 않는다. 하지만 Int 32 로는 최대 32개의 속성을 지정할 수 있다. 더 많은 속성이 필요하다면 Int 64 자료형을 사용하고 그보다 더 많은 속성이 필요하면... 다른 방법을 찾아봐야한다.