열거형


열거형은 가능한 값의 집합을 사용자가 직접 정의하는 타입이다. 내장형(Built In Type)은 시스템이 미리 정의해 놓은 범위의 값만을 가지는데 비해 열거형은 사용자가 값의 종류를 정의할 수 있기 때문에 사용자 정의형(User Defined Type)이라고 한다. 변수가 가질 수 있는 값이 특정한 유한 집합으로 제한될 때는 열거형을 사용하는 것이 좋다.

열거형은 값에 이름을 줄 수 있어 가독성에 유리하며 집합에 속하지 않은 엉뚱한 값을 대입할 수 없기 때문에 안전하다. 또한 인텔리센스의 도움을 받을 수 있어 사용하기도 편리하다. 다른 언어의 열거형과 사용 목적이나 개념은 동일하지만 문법은 다소 차이가 있다. 열거형 변수를 사용하려면 먼저 열거 타입을 정의해야 한다. enum 키워드 다음에 열거 타입의 이름을 쓰고 { } 괄호안에 열거 멤버를 콤마로 구분하여 나열한다. 다음은 방향을 나타내는 열거 타입 Origin을 정의한다.

enum Origin { East, West, South, North }

방향은 동서남북 넷 중 하나의 값을 가지며 그 외의 값을 가질 수 없으므로 열거형으로 정의하는 것이 합당하다. 열거 멤버의 이름은 자유롭게 붙일 수 있되 명칭이므로 명칭 규칙은 지켜야 한다. 열거형 변수에 값을 대입할 때는 열거타입.멤버 식으로 대입한다. 다음은 Origin 타입의 변수 Turn을 선언하고 Turn이 남쪽을 가리키도록 초기화한다

Origin Turn;

Turn = Origin.South;

C#의 열거형은 C/C++ 문법과 다른 점이 몇 가지 있다. 우선 타입을 정의하는 문장은 명령이 아니기 때문에 열거형 정의문 뒤에 세미콜론을 붙이지 않는다. 그리고 열거 멤버 자체를 곧바로 사용할 수 없으며 반드시 열거타입.멤버 식으로 사용해야 한다. C에서는 열거 멤버가 하나의 상수로 정의되므로 Turn = South; 같은 대입문이 가능하다.

열거 멤버들은 0부터 시작되는 정수값을 가지며 오른쪽 멤버로 가면서 차례대로 값이 1씩 증가한다. Origin 열거형에서는 East가 0이고 West가 1이며 South, North는 각각 2와 3의 값을 가진다. 어차피 열거 멤버는 구분이 목적이므로 중복되지만 않는다면 값 자체에는 큰 의미가 없다. 열거 멤버에 특정한 값을 지정하려면 열거 멤버 다음에 = 과 원하는 값을 쓰면 된다. 값이 생략된 멤버는 자동적으로 이전 멤버의 값보다 1 더 큰 값을 가진다.

enum Origin { East = 1, West = 5, South, North }

위 정의문에서 East와 West는 지정한 값 1, 5를 가지며 값을 지정하지 않은 South, North는 5의 다음 값인 6과 7로 정의된다. 열거형이 구분만을 목적으로 한다면 특별히 값을 지정하지 않아도 0, 1, 2, 3 식으로 중복되지 않은 값이 매겨지므로 별 상관없다. 그러나 특별한 의미를 가진다면 값에 변화를 줄 수 있는데 예를 들어 등급을 나타낸다면 1, 2, 3, 4 식의 값을 가지는 것이 좋을 것이다. 실생활에 사용되는 자연수는 0부터 시작하지 않고 1부터 시작하므로 자연수에 맞춰 놓으면 여러 모로 편리해진다.

열거형은 내부적으로 정수형으로 저장되는데 별다른 지정이 없으면 4바이트의 int 타입을 가진다. int 타입의 열거형은 최대 40억개의 열거 멤버를 정의할 수 있어 크기가 충분하다. 그러나 너무 충분해서 기억 장소가 다소 낭비되는데 선언할 때 열거 타입 이름 다음에 내부 저장 타입을 지정하면 용량을 다소 아낄 수 있다.

enum Origin : byte { East, West, South, North }

이렇게 선언하면 Origin 타입은 1바이트밖에 되지 않아 4바이트의 int 타입보다는 메모리를 적게 차지한다. 이 경우 열거 멤버의 개수는 256개 이상이 될 수 없으며 음수나 256 이상의 값을 가지지 못한다. 보통의 경우는 고작 3바이트를 아끼자고 이런 구두쇠짓을 할 필요가 없지만 열거형의 대용량 배열이 필요할 때는 이 차이를 결코 무시할 수 없다.

한 열거형 안에 똑같은 이름의 열거 멤버를 정의할 수는 없다. Origin안에 East가 두 개 있으면 East가 0인지 1인지 모호해지므로 당연한 규칙이며 사실 같은 열거 멤버가 존재해야 할 이유도 없다. 그러나 다른 이름으로 똑같은 값을 가지는 열거 멤버를 만드는 것은 가능하다. 이 경우 열거 멤버의 값을 결정하는데는 문제가 없으며 일종의 동의어를 정의한다고 볼 수 있다.

enum Origin : byte { East, East, West, South, North }                    // 에러
enum Origin : byte { East = 1, West = 1, South, North }                  // 가능
enum Origin : byte { East, West, South, North, Dong = East }              // 가능


첫 번째 선언은 East가 둘이므로 Origin.East의 값이 모호해서 에러로 처리된다. 두 번째 선언은 East와 West가 둘 다 1의 값을 가지는데 논리적으로는 좀 이상해 보이지만 문법적으로는 문제가 없다. 어떤 이유로 동쪽과 서쪽이 똑같은 의미를 가질 수도 있는 것이다. 세 번째 선언은 East와 똑같은 값을 가지는 Dong이라는 동의어를 정의한다. 이 열거형에서 동쪽은 Origin.East로 표현할 수도 있고 Origin.Dong으로 표현할 수도 있다.

열거형은 내부적으로 정수형으로 저장되며 실제로 정수형과 포맷이 일치하기 때문에 정수형 변수와 값을 교환할 수 있다. 하지만 두 타입이 분명히 다르기 때문에 명시적인 캐스팅이 필요하다. 이 경우 캐스팅에 의한 값 손실은 전혀 없으므로 별로 위험하지는 않다. 다음 예제는 정수형과 열거형의 값 대입과 출력을 테스트한다. 



Origin 타입의 변수 Turn을 선언하고 Origin.South를 대입하여 이 변수의 값을 WriteLine으로 출력해 보았다. WriteLine 함수는 열거 멤버의 실제값이 아닌 이름을 출력한다. Turn을 (int)로 캐스팅하면 정수형 변수 Value에 이 값을 대입할 수 있다. 대입 후 Value값을 WriteLine으로 출력해 보면 South의 실제값이 출력 된다. 출력 결과는 다음과 같다.

South
2

정수를 Origin 타입으로 대입할 때도 마찬가지로 캐스팅이 필요하다. Turn = (Origin)Value;대입문은 정수 Value를 Origin 타입으로 캐스팅하여 Turn에 대입한다. Value가 Origin 타입의 변수에 정상적으로 대입되기 위해서는 유효한 범위, 이 경우 0~3의 값 중 하나를 가져야 한다. 만약 범위를 벗어나는 엉뚱한 값을 가지면 일단 대입은 되지만 Turn의 상태가 무효해지며 이는 잠재적인 버그의 원인이 될 수 있다. 강제 캐스팅이란 이처럼 위험한 것이며 그래서 암시적 변환을 금지하는 것이다.

열거형은 System.Enum 구조체이며 Enum에는 열거형을 관리하는 메서드들이 준비되어 있다. ToInt32, ToUInt32, ToDouble 등 To로 시작하는 메서드들은 열거형을 다른 타입으로 변환하는데 이중 특히 문자열로 변환하는 ToString이 실용적이다. Parse 메서드는 반대로 다른 타입의 값을 열거형으로 변환한다. IsDefined는 열거형내에 특정 열거 멤버가 존재하는지를 조사한다. 



Turn을 문자열로 바꾸어 출력해 보고 문자열 "East"를 Turn에 대입한 후 Turn을 출력해 보았다. 이 방법을 사용하면 실행중에 사용자로부터 열거형값을 직접 입력받을 수도 있다. 실행 결과는 다음과 같다.

South
East

뭔 당연한 소리냐고 할지 모르겠지만 이는 다소 놀라운 결과이다. 열거형이 내부적으로 정수로 기억되지만 열거 멤버의 이름을 실행중에 알아낼 수도 있다. C 언어에서는 실행중에 열거형 변수의 열거 상수를 알아낼 방법이 전혀 없다. C#에서 이것이 가능한 이유는 실행중에 타입의 정보를 조사하는 기능이 있기 때문이다.

'Study > C#' 카테고리의 다른 글

C# 타입 - 2. 참조 타입 (배열)  (0) 2011.11.22
C# 타입 - 1. 값 타입 (구조체)  (0) 2011.11.22
C# 타입 - 1. 값 타입 (내장형)  (0) 2011.11.09
C# 타입 - 1. 값 타입 (타입의 종류)  (0) 2011.11.08
C# Main 함수  (0) 2011.11.07
Posted by 코딩하는 야구쟁이
,