상태값이나 구분 코드를 숫자로 들고 다니면 몇 달 뒤 코드를 다시 열었을 때 If status = 3 Then 이 무슨 뜻인지 기억이 안 납니다. DB에 저장된 코드값, 콤보박스 선택 항목, 권한 구분 같은 걸 다룰 때 Enum 으로 이름을 붙여 두면 읽기도 쉽고 오타로 엉뚱한 숫자를 넣는 실수도 컴파일 단계에서 잡힙니다.

선언 형식은 이렇습니다. 대괄호 부분은 생략할 수 있습니다.

광고
[ <attributelist> ] [ accessmodifier ] [ Shadows ] Enum enumerationname [ As datatype ]
    memberlist
End Enum

각 멤버는 [<attribute list>] membername [ = initializer ] 형식으로 한 줄에 하나씩 적습니다.

요소 필수 여부 의미
attributelist 선택 열거형 전체에 붙일 특성. 대표적으로 <Flags()>
accessmodifier 선택 Public, Protected, Friend, Private, Protected Friend, Private Protected 중 하나
Shadows 선택 기본 클래스에 같은 이름의 요소가 있을 때 그것을 가림. 열거형 자체에만 붙일 수 있음
enumerationname 필수 열거형 이름
datatype 선택 열거형과 모든 멤버의 데이터 형식
memberlist 필수 멤버 상수 목록
membername 필수 멤버 이름
initializer 선택 컴파일 시점에 계산되어 멤버에 들어갈 식

값을 안 적으면 몇이 들어가나

멤버에 값을 주지 않으면 첫 멤버는 0, 그다음부터는 바로 앞 멤버보다 1 큰 값이 들어갑니다. 아래 EggSizeEnum 이면 Jumbo가 0, Small이 4입니다. 클래스 안에 선언한 열거형은 같은 클래스 안에서는 EggSizeEnum.Medium 처럼 쓰면 되고, 멤버 이름만 단독으로 Medium 이라고 쓸 수는 없습니다.

Public Class Egg
    Enum EggSizeEnum
        Jumbo
        ExtraLarge
        Large
        Medium
        Small
    End Enum

    Public Sub Poach()
        Dim size As EggSizeEnum
        size = EggSizeEnum.Medium
        ' Continue processing...
    End Sub
End Class

클래스 바깥에서 쓰려면 클래스 이름까지 붙여 Egg.EggSizeEnum 으로 적어야 합니다. 매번 길게 쓰기 싫으면 파일 위에 Imports 를 추가하면 됩니다. 처리할 수 없는 값이 들어왔을 때 Case Else 에서 예외를 던지는 패턴도 같이 봐 두세요. 나중에 멤버를 추가하고 Select Case를 깜빡 안 고쳤을 때 조용히 지나가지 않게 해 줍니다.

Public Sub Scramble(ByVal size As Egg.EggSizeEnum)
    ' Process for the three largest sizes.
    ' Throw an exception for any other size.
    Select Case size
        Case Egg.EggSizeEnum.Jumbo
            ' Process.
        Case Egg.EggSizeEnum.ExtraLarge
            ' Process.
        Case Egg.EggSizeEnum.Large
            ' Process.
        Case Else
            Throw New ApplicationException("size is invalid: " & size.ToString)
    End Select
End Sub

값을 직접 지정하는 것도 물론 됩니다. 색상값처럼 의미 있는 숫자에 이름을 붙이거나, 음수를 섞어 쓸 수도 있어요. 아래 색상값은 &H 16진수 리터럴이고, 끝의 & 는 Long 리터럴 표시입니다.

Public Enum InterfaceColors
    MistyRose = &HE1E4FF&
    SlateGray = &H908070&
    DodgerBlue = &HFF901E&
    DeepSkyBlue = &HFFBF00&
    SpringGreen = &H7FFF00&
    ForestGreen = &H228B22&
    Goldenrod = &H20A5DA&
    Firebrick = &H2222B2&
End Enum
Enum SecurityLevel
    IllegalEntry = -1
    MinimumSecurity = 0
    MaximumSecurity = 1
End Enum

DB 코드값과 맞출 때는 저는 값을 전부 명시적으로 적는 편입니다. 자동 번호에 맡겨 두면 누가 중간에 멤버를 하나 끼워 넣는 순간 뒤쪽 값이 전부 밀려서, 이미 저장된 데이터와 어긋나 버리거든요.

initializer에는 리터럴, 이미 정의된 상수, 앞서 정의된 열거형 멤버를 산술·논리 연산자로 조합한 식을 쓸 수 있습니다. 변수나 일반 함수 호출은 안 되지만 CByte, CShort 같은 변환 키워드와, 상수 문자 인수를 준 AscW 는 컴파일 시점에 계산되므로 쓸 수 있습니다.

데이터 형식과 접근 수준

As 절로 지정할 수 있는 형식은 Byte, Integer, Long, SByte, Short, UInteger, ULong, UShort입니다. 정수형만 되고 부동소수점은 안 됩니다. Option Strict On 상태에서 실수값을 넣으면 컴파일 에러가 나고, Off면 자동으로 변환됩니다. 형식을 지정하지 않으면 initializer의 형식을 따르고, 둘 다 없으면 Integer입니다. 멤버 값이 지정한 형식의 범위를 넘으면 컴파일 에러가 납니다.

Public Enum MyEnum As Byte
  Zero
  One
  Two
End Enum

접근 수준은 클래스, 구조체, 모듈, 인터페이스 안에 선언하면 기본이 Public이고, 네임스페이스 바로 아래에 선언하면 기본이 Friend입니다. 네임스페이스 수준 열거형은 Public으로 올릴 수는 있어도 Private나 Protected로는 못 바꿉니다. 멤버에는 접근 한정자를 따로 붙일 수 없고, 열거형 자체의 접근 수준을 그대로 따릅니다.

선언 위치에도 제약이 있습니다. 소스 파일, 네임스페이스, 클래스, 구조체, 모듈, 인터페이스 수준에서만 선언할 수 있고 Sub나 Function 안에서는 선언할 수 없습니다. 열거형은 그 자체가 형식이고 멤버는 상수라서 Shared, Static, ReadOnly 도 붙일 수 없습니다. 특성은 열거형 전체에만 붙일 수 있고 개별 멤버에는 따로 붙일 수 없습니다.

하나의 변수에 여러 값을 담으려면 Flags

보통 열거형 변수는 멤버 중 하나만 가집니다. 그런데 파일 권한처럼 "읽기도 되고 수정도 되는" 조합을 표현해야 할 때가 있죠. 이럴 땐 <Flags()> 특성을 붙이고 값을 1, 2, 4, 8, 16처럼 2의 거듭제곱으로 매깁니다. 각 멤버가 비트 하나씩을 차지하게 되어 Or 로 합치고 HasFlag 로 확인할 수 있습니다. 값이 0인 멤버는 None 으로 이름 붙이는 게 관례입니다.

' Apply the Flags attribute, which allows an instance
' of the enumeration to have multiple values.
<Flags()> Public Enum FilePermissions As Integer
    None = 0
    Create = 1
    Read = 2
    Update = 4
    Delete = 8
End Enum

Public Sub ShowBitwiseEnum()

    ' Declare the non-exclusive enumeration object and
    ' set it to multiple values.
    Dim perm As FilePermissions
    perm = FilePermissions.Read Or FilePermissions.Update

    ' Show the values in the enumeration object.
    Console.WriteLine(perm.ToString)
    ' Output: Read, Update

    ' Show the total integer value of all values
    ' in the enumeration object.
    Console.WriteLine(CInt(perm))
    ' Output: 6

    ' Show whether the enumeration object contains
    ' the specified flag.
    Console.WriteLine(perm.HasFlag(FilePermissions.Update))
    ' Output: True
End Sub

ToString 결과가 "Read, Update" 처럼 이름을 쉼표로 이어서 보여 주는 것도 Flags 를 붙였을 때의 동작입니다. 권한 값을 DB에는 정수 6 하나로 저장하고, 화면에서는 체크박스 여러 개로 풀어 보여 주는 식으로 많이 씁니다.

콤보박스에 멤버 목록 넣기

열거형 멤버를 전부 돌면서 이름이나 값을 꺼내야 할 때는 [Enum].GetNames 와 [Enum].GetValues 를 씁니다. Enum 이 키워드라서 대괄호로 감싸야 한다는 점만 기억하면 됩니다.

Enum EggSizeEnum
    Jumbo
    ExtraLarge
    Large
    Medium
    Small
End Enum

Public Sub Iterate()
    Dim names = [Enum].GetNames(GetType(EggSizeEnum))
    For Each name In names
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Jumbo ExtraLarge Large Medium Small

    Dim values = [Enum].GetValues(GetType(EggSizeEnum))
    For Each value In values
        Console.Write(value & " ")
    Next
    Console.WriteLine()
    ' Output: 0 1 2 3 4
End Sub

윈폼에서는 ComboBox1.DataSource = [Enum].GetValues(GetType(EggSizeEnum)) 한 줄로 콤보박스에 멤버를 채우고, 선택값은 CType(ComboBox1.SelectedItem, EggSizeEnum) 으로 다시 받아 쓰면 편합니다. 반대로 DB에서 읽은 문자열을 열거형으로 바꿀 때는 [Enum].TryParse 를 쓰면 잘못된 값이 들어와도 예외 없이 걸러낼 수 있습니다.

참고: Microsoft Learn - Enum 문(Visual Basic)