열거형(enum)은 값이 이름 붙은 상수의 고정된 집합인 타입입니다. 주문 상태, 요일, 로그 수준 같은 것입니다. 내부적으로 각 이름은 정수이지만, 타입 시스템이 OrderStatus를 일반 int나 다른 열거형과 섞이지 않게 해 줍니다.
열거형 선언과 사용
중괄호 안에 멤버 이름을 나열합니다. 기본적으로 첫 번째가 0이고 다음 것은 하나씩 커집니다:
출력:
Paid
On its way
True
2
열거형은 진짜 타입입니다. OrderStatus를 받는 메서드를 실수로 3이나 LogLevel로 호출할 수 없습니다. 열거형은 값 형식이므로 절대 null이 아니고 ==로 값을 비교합니다.
명시적 값과 기반 타입
숫자를 직접 대입할 수 있습니다. 숫자가 프로그램 밖으로 나갈 때(데이터베이스 열, HTTP 상태, 파일 형식)는 번호를 다시 매기면 저장된 데이터가 깨지므로 중요합니다:
출력:
404
Created
418
1
Byte
주목할 것이 두 가지 있습니다. int를 enum으로 캐스트하는 것은 절대 실패하지 않습니다. (HttpStatus)418은 이름이 없을 뿐인 올바른 값이고 숫자로 출력됩니다. 그리고 기반 타입은 어떤 정수 타입(byte, short, long, ...)이든 될 수 있으며, 저장 공간에 민감한 코드에서만 중요합니다. int가 기본값이고 거의 항상 맞는 선택입니다.
나중에 멤버를 추가할 때는 끝에 추가하거나 명시적 값을 주세요. Paid와 Shipped 사이에 Refunded를 끼워 넣으면 그 뒤 모든 멤버의 번호가 조용히 바뀝니다.
enum을 문자열로
ToString()은 멤버 이름을 반환하며, Console.WriteLine과 문자열 보간이 쓰는 것도 이것입니다. 서식 문자열로 출력을 바꿀 수 있습니다:
출력:
Warning
2
00000002
[Warning]
Error
Error
Needs attention
멤버 이름은 식별자이므로 공백을 담을 수 없고 번역되지도 않습니다. 사용자에게 보여 주는 텍스트는 Label처럼, 또는 Dictionary<LogLevel, string>으로 값을 직접 대응시키세요. 일부 코드베이스는 각 멤버에 [Description("Needs attention")] 특성을 붙이고 리플렉션으로 읽습니다. 그 조회 방법은 리플렉션과 특성 페이지에서 보여 줍니다.
문자열을 enum으로: Parse와 TryParse
Enum.Parse는 이름을 다시 값으로 바꾸며, 맞는 것이 없으면 ArgumentException을 던집니다. Enum.TryParse는 대신 false를 반환하며, 통제할 수 없는 모든 입력에 필요한 것이 이것입니다:
출력:
Large
Medium
Parse threw ArgumentException for Huge
small parsed=True value=Small defined=True
XL parsed=False value=Small defined=True
2 parsed=True value=Large defined=True
7 parsed=True value=7 defined=False
마지막 두 행이 함정입니다. 두 메서드 모두 숫자 문자열을 받으므로, "7"은 이름 없는 Size로 성공적으로 파싱됩니다. 그리고 실패한 TryParse는 결과를 0으로 설정하는데, 여기서는 올바라 보이는 Small입니다. 텍스트가 쿼리 문자열, 설정 파일, 폼에서 온다면 반환값과 Enum.IsDefined를 모두 확인하세요:
if (Enum.TryParse(input, true, out Size size) && Enum.IsDefined(typeof(Size), size))
{
// safe to use size
}
.NET Core 2.0 이상에는 캐스트가 필요 없는 제네릭 Enum.Parse<Size>("Large")가 있습니다.
모든 값 나열하기
Enum.GetValues는 모든 멤버를 숫자 값 순서로 정렬해 반환하고(부호 없는 값으로 비교하므로 음수 멤버는 마지막에 옵니다), Enum.GetNames는 그 이름들을 반환합니다. 드롭다운을 채우거나 모든 선택지에 대해 검증하는 방법입니다:
출력:
Free 0 EUR/month
Starter 9 EUR/month
Pro 29 EUR/month
Team 99 EUR/month
Free | Starter | Pro | Team
3 paid plans
Enum.GetValues(typeof(Plan))은 일반 Array를 반환하므로 LINQ 전에 Cast<Plan>()이 필요합니다. .NET 5 이상에서는 Enum.GetValues<Plan>()이 타입이 지정된 Plan[]을 바로 반환합니다.
Flags: 값 조합하기
어떤 열거형은 하나의 선택이 아니라 옵션의 집합을 나타냅니다. 파일 권한, 가게가 여는 요일, 알림 채널 같은 것입니다. 각 멤버에 자기 비트(1, 2, 4, 8, ...)를 주고, None = 0을 추가하고, 열거형을 [Flags]로 표시하세요. 그러면 값을 |로 조합할 수 있습니다:
출력:
Read, Share
Editor, Share
True
False
Editor
3
Read, Delete
True
각 연산자가 하는 일: |는 비트를 켜고, & ~X는 끄고, ^는 뒤집으며, (value & X) != 0이나 value.HasFlag(X)는 검사합니다. HasFlag(X)는 "X의 모든 비트가 켜져 있다"는 뜻이므로 HasFlag(None)은 모든 값에서 참이고, HasFlag(Editor)는 Read와 Write가 모두 필요합니다.
두 번째 줄에 주목하세요. 이름 붙은 조합이 켜진 비트 일부를 덮으면 ToString이 그것을 쓰므로, Read | Write | Share는 Editor, Share로 출력됩니다. Enum.Parse가 아닌 다른 방법으로 ToString의 출력을 파싱하기 전에 이 점을 기억하세요.
특성은 산술을 바꾸지 않습니다. 서식을 바꿉니다. [Flags]가 없으면 그 값을 가진 멤버가 없으므로 Read | Share는 9로 출력됩니다. 있으면 ToString과 Parse가 모두 쉼표로 구분된 형태로 동작합니다. 멤버는 여전히 2의 거듭제곱이어야 합니다. 기본 번호(0, 1, 2)로 Read, Write, Delete를 쓰면 Write | Delete가 무의미한 값 3이 됩니다.
열거형으로 switch하기
열거형에 따라 동작할 때는 switch가 자연스럽습니다. 열거형 변수는 이름 없는 값을 담을 수 있으므로 default 분기를 넣으세요:
switch (status)
{
case OrderStatus.Pending:
case OrderStatus.Paid:
return "Preparing";
case OrderStatus.Shipped:
return "On the way";
case OrderStatus.Delivered:
return "Delivered";
default:
return "Unknown";
}
C# 8부터는 switch 식이 더 짧습니다. _ 갈래가 없으면 컴파일러가 경고합니다. 이름 붙은 멤버가 빠지면 CS8509, 모든 이름을 처리했지만 (OrderStatus)7 같은 이름 없는 값이 처리되지 않으면 CS8524입니다:
string text = status switch
{
OrderStatus.Pending or OrderStatus.Paid => "Preparing", // 'or' pattern: C# 9
OrderStatus.Shipped => "On the way",
OrderStatus.Delivered => "Delivered",
OrderStatus.Cancelled => "Cancelled",
_ => throw new ArgumentOutOfRangeException(nameof(status)),
};
기본값과 정의되지 않은 값
모든 열거형의 기본값은 그 값을 가진 멤버가 있든 없든 0입니다. 필드, 배열 요소, 실패한 TryParse가 모두 이 값을 만듭니다. 이를 고려해 설계하세요:
0을 진짜 선택지가 아니라 의미 있는 "설정 안 됨" 멤버(None,Unknown)로 만드세요. 그렇지 않으면 초기화되지 않은 필드가 조용히 첫 번째 진짜 선택지로 읽힙니다.- 외부에서 온 숫자는
Enum.IsDefined로 검증하세요.[Flags]열거형에서는IsDefined가 이름 없는 조합(Read | Share)에 대해false를 반환하므로, 대신 모든 비트를 덮는All멤버와 함께 비트를 검사하세요:(value & ~Permissions.All) == 0.
흔한 실수
TryParse만 믿기. 숫자 문자열이 파싱되고, 실패한 파싱은0을 줍니다.Enum.IsDefined를 추가하세요.- 저장되는 값에 암묵적 번호에 의존하기. 멤버를 끼워 넣으면 그 뒤의 번호가 바뀝니다. 저장되는 열거형에는 명시적 값을 대입하세요.
- 2의 거듭제곱이 아닌 Flags. 기본 번호(0, 1, 2, 3)는 비트가 겹칩니다. 1, 2, 4, 8 또는
1 << n을 쓰세요. - 사용자에게
ToString()보여 주기. 멤버 이름은 코드 식별자입니다. 값을 표시용 텍스트에 대응시키세요. - switch에
default가 없음. 열거형은 이름 붙은 멤버 밖의 값을 담을 수 있습니다.
자주 묻는 질문
C#에서 enum을 문자열로 바꾸려면 어떻게 하나요?
ToString()을 호출하세요. OrderStatus.Shipped.ToString()은 "Shipped"를 반환하고, 문자열 보간도 같은 일을 합니다. ToString("D")는 대신 숫자를 줍니다. 컴파일 시점에 알려진 이름이라면 nameof(OrderStatus.Shipped)가 상수입니다. 공백이 있거나 번역된 사용자용 텍스트에는 멤버 이름에 의존하지 말고 값을 문자열에 직접 대응시키세요(switch나 딕셔너리).
C#에서 문자열을 enum으로 바꾸려면 어떻게 하나요?
Enum.TryParse<OrderStatus>(text, true, out var status)를 쓰세요. 텍스트가 어떤 멤버와도 맞지 않으면 예외 대신 false를 반환합니다(true는 대소문자를 무시하게 합니다). Enum.Parse(typeof(OrderStatus), text)는 잘못된 입력에 ArgumentException을 던집니다. 둘 다 "42" 같은 숫자 문자열도 받으므로, 입력이 사용자에게서 온다면 Enum.IsDefined로 결과를 확인하세요.
C#에서 enum과 int 사이는 어떻게 변환하나요?
어느 방향이든 캐스트합니다: int code = (int)OrderStatus.Paid;와 var status = (OrderStatus)2;. int에서의 캐스트는 대응하는 멤버가 없는 숫자에서도 절대 실패하지 않으며, 결과는 숫자로 출력되는 enum 값입니다. 숫자가 외부에서 온다면 Enum.IsDefined(typeof(OrderStatus), value)로 검증하세요.
C#에서 enum의 모든 값을 순회하려면 어떻게 하나요?
foreach (OrderStatus s in Enum.GetValues(typeof(OrderStatus)))는 모든 멤버를 숫자 값 순서로 방문합니다. .NET 5부터는 캐스트가 필요 없는 제네릭 버전 Enum.GetValues<OrderStatus>()가 있습니다. Enum.GetNames(typeof(OrderStatus))는 이름을 문자열로 반환합니다.
C# enum에서 [Flags]는 무엇을 하나요?
Read | Write처럼 |로 조합하기 위한 비트 값을 가진 enum을 표시합니다. 각 멤버에 2의 거듭제곱(1, 2, 4, 8)을 주고 None = 0을 두세요. 이 특성은 ToString()이 조합을 "Read, Write"로 출력하게 하고 Enum.Parse가 그 형식을 다시 읽게 해 줍니다. 비트는 HasFlag나 (value & Permissions.Write) != 0으로 검사합니다.