[C#] 18. 열거형(enum)을 사용하는 방법


Study/C#  2020. 7. 27. 17:48

안녕하세요. 명월입니다.


이 글은 C#에서 열거형(enum)을 사용하는 방법에 대한 글입니다.


우리가 프로그램을 작성할 때, 고정된 값을 사용하는 경우가 있습니다. 그럴 때 변수의 상수화(값이 변하지 않는 변수)를 통해 값을 정의하고 사용해도 됩니다.

링크 - [C#] 02. 프로그래밍 시작과 변수와 상수를 선언하는 방법

using System;
namespace Example
{
  class Program
  {
    // 상수 선언
    public const int TYPE1 = 1;
    public const int TYPE2 = 2;
    // 함수 호출
    private static void Print(int type)
    {
      // 파라미터 값이 TYPE1이면
      if (type == TYPE1)
      {
        // 콘솔 출력
        Console.WriteLine("Print Type1");
      }
      // 파라미터 값이 TYPE2이면
      else if (type == TYPE2)
      {
        // 콘솔 출력
        Console.WriteLine("Print Type2");
      }
      // 그 외
      else
      {
        // 콘솔 출력
        Console.WriteLine("Other");
      }
    }

    // 실행 함수
    public static void Main(string[] args)
    {
      // 함수 호출
      Print(TYPE1);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제를 보면 Print라는 함수에 파라미터가 int형이니 그냥 1이나 2를 넣어도 문제없이 작동합니다.

그러나 실제 프로젝트에서 코딩할 때 그냥 숫자 1이나 2를 넣으면 다음에 코드를 볼 때, 무슨 의미인지 모르는 경우가 많습니다. 그래서 의미를 알기 위해 상수로 변환을 해서 1이나 2보다는 조금은 알기 쉽게 문자형으로 만드는 것입니다.

위 예제에서는 제가 TYPE1과 TYPE2의 상수를 만들어서 의미를 만든 것입니다. 실제도로 이런식으로 많이 작성합니다만... 이것도 가독성의 한계가 있습니다.

using System;
namespace Example
{
  class Program
  {
    // 상수 선언
    // typeA의 상수
    public const int ATYPE1 = 1;
    public const int ATYPE2 = 2;
    // typeB의 상수
    public const int BTYPE1 = 1;
    public const int BTYPE2 = 2;
    // 함수 호출
    private static void Print(int typeA, int typeB)
    {
      // typeA 파라미터 값이 ATYPE1이면
      if (typeA == ATYPE1)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 1");
      }
      // typeA 파라미터 값이 ATYPE2이면
      else if (typeA == ATYPE2)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 2");
      }
      // typeB 파라미터 값이 BTYPE1이면
      if (typeB == BTYPE1)
      {
        // 콘솔 출력
        Console.WriteLine("Print BTYPE - 1");
      }
      // typeB 파라미터 값이 BTYPE2이면
      else if (typeB == BTYPE2)
      {
        // 콘솔 출력
        Console.WriteLine("Print BTYPE - 2");
      }
    }

    // 실행 함수
    public static void Main(string[] args)
    {
      // 함수 호출
      Print(ATYPE1, BTYPE1);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

typeA의 파라미터에는 ATYPE1와 ATYPE2를 사용하고 typeB의 파라미터에는 BTYPE1와 BTYPE2를 사용합니다.

A type의 파라미터 B type의 파라미터를 구분해 놓았습니다. 그러나 이 파라미터의 구분은 모두 int형이기 때문에 숫자로 들어가는 것도 가능합니다만 A type의 파라미터에 BTYPE1의 상수를 사용하는 것도 문제 없습니다.

전혀 에러가 발생하거나 문제가 되지 않습니다. 다만 소스를 해독하는 입장에서 의미가 이상해집니다. 가독성이 매우 떨어지는 것입니다.


이것을 정리할 수 있는게 열거형 타입입니다.

using System;
namespace Example
{
  class Program
  {
    enum TypeA
    {
      TYPE1,
      TYPE2
    }
    enum TypeB
    {
      TYPE1,
      TYPE2
    }
    // 함수 호출
    private static void Print(TypeA typeA, TypeB typeB)
    {
      // typeA 파라미터 값이 TYPE1이면
      if (typeA == TypeA.TYPE1)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 1");
      }
      // typeA 파라미터 값이 TYPE2이면
      else if (typeA == TypeA.TYPE2)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 2");
      }
      // typeB 파라미터 값이 TYPE1이면
      if (typeB == TypeB.TYPE1)
      {
        // 콘솔 출력
        Console.WriteLine("Print BTYPE - 1");
      }
      // typeB 파라미터 값이 TYPE2이면
      else if (typeB == TypeB.TYPE2)
      {
        // 콘솔 출력
        Console.WriteLine("Print BTYPE - 2");
      }
    }

    // 실행 함수
    public static void Main(string[] args)
    {
      // 함수 호출
      Print(TypeA.TYPE1, TypeB.TYPE2);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

상수형을 열거형으로 묶어서 TypeA의 파라미터에 TypeB를 넣거나 1를 넣는 가독성에 위반되는 코딩을 하지 않고 깔끔하게 만들어 집니다.


열거형은 단순히 값을 가지는 것이 아니고 이렇게 소스의 가독성을 높히고 소스 안에서 사용되는 Flag를 위한 키워드입니다만, C++에서 자주 사용하던 bit flags 형식도 가능합니다.

using System;
namespace Example
{
  class Program
  {
    // 비트 연산자의 값으로 표현한다.
    enum TypeA
    {
      // 0000 0000
      None = 0x00,
      // 0000 0001
      TYPE1 = 0x01,
      // 0000 0010
      TYPE2 = 0x02,
      // 0000 0100
      TYPE3 = 0x04,
      // 0000 1000
      TYPE4 = 0x08,
      // 0001 0000
      TYPE5 = 0x10,
      // 0010 0000
      TYPE6 = 0x20,
      // 0100 0000
      TYPE7 = 0x40,
      // 1000 0000
      TYPE8 = 0x80
    }
    // 함수 호출
    private static void Print(TypeA typeA)
    {
      // typeA 파라미터 값이 TYPE1이면
      if ((typeA & TypeA.TYPE1) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 1");
      }
      // typeA 파라미터 값이 TYPE2이면
      if ((typeA & TypeA.TYPE2) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 2");
      }
      // typeA 파라미터 값이 TYPE2이면
      if ((typeA & TypeA.TYPE3) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 3");
      }
      // typeA 파라미터 값이 TYPE2이면
      if ((typeA & TypeA.TYPE4) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 4");
      }
      // typeA 파라미터 값이 TYPE2이면
      if ((typeA & TypeA.TYPE5) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 5");
      }
      // typeA 파라미터 값이 TYPE2이면
      if ((typeA & TypeA.TYPE6) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 6");
      }
      // typeA 파라미터 값이 TYPE2이면
      if ((typeA & TypeA.TYPE7) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 7");
      }
      // typeA 파라미터 값이 TYPE2이면
      if ((typeA & TypeA.TYPE8) != 0)
      {
        // 콘솔 출력
        Console.WriteLine("Print AType - 8");
      }
    }

    // 실행 함수
    public static void Main(string[] args)
    {
      // 함수 호출
      Print(TypeA.TYPE1 | TypeA.TYPE4 | TypeA.TYPE7);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

열겨형에 int형의 값을 설정할 수 있는데 비트 연산자를 이용해서 하나의 변수에 여러 개의 플래그를 표현할 수 있습니다.

using System;
namespace Example
{
  class Program
  {
    // 비트 연산자의 값으로 표현한다.
    enum TypeA
    {
      // 0000 0000
      None = 0x00,
      // 0000 0001
      TYPE1 = 0x01,
      // 0000 0010
      TYPE2 = 0x02,
      // 0000 0100
      TYPE3 = 0x04,
      // 0000 1000
      TYPE4 = 0x08,
      // 0001 0000
      TYPE5 = 0x10,
      // 0010 0000
      TYPE6 = 0x20,
      // 0100 0000
      TYPE7 = 0x40,
      // 1000 0000
      TYPE8 = 0x80
    }
    // 함수 호출
    private static void Print(TypeA typeA)
    {
      // 콘솔 출력(TypeA의 열거형을 그대로 ToString하면 열거형의 문자열이 나온다.)
      Console.WriteLine(typeA.ToString());
    }

    // 실행 함수
    public static void Main(string[] args)
    {
      // 함수 호출
      Print(TypeA.TYPE1);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

그리고 열거형의 값을 그대로 ToString하면 열거형의 값을 출력할 수 있습니다.


저는 신입때 예전 사수로 부터 프로그램 코딩을 할 때, 상수와 변수 선언이외에 함수 안에서는 숫자가 들어가면 그건 하드 코딩이고 설계 및 코딩 실패라고 배웠었습니다. (하드 코딩이라는 게 꼭 숫자를 넣는다는게 하드 코딩의 뜻은 아닙니다.)

그래서 선임이 프로그램 작업하면 마무리로 항상 상수 변수를 선언하여 치원하던 일을 했던게 기억이 나네요.. 프로그램 안에 숫자를 넣었다고 그게 설계 실패까지는 아닙니다.

상황에 따라서 그냥 숫자 1이라는 것이 더 확실하게 이해하는 경우도 있습니다. 매우 드물기는 하지만 있습니다.

그러나 그 외에는 이렇게 상수 작업이 필수입니다.


큰 프로젝트는 프로그램을 혼자하는 경우는 없습니다. 꼭 큰 프로젝트가 아닌 혼자하는 프로젝트라고 해도 하드 코딩이 많아지게 되면 작성할 때는 알아도 한달만 지나도 내가 작성한 코드를 보면서 암호를 해독하는 경우도 있습니다.

그렇기 때문에 소스 가독성이라는 건 매우 중요합니다. 물론 가독성을 위해 주석을 다는 것도 중요하지만 주석에 매번 모든 걸 표시할 수도 없고.. 주석이 많은 소스라고 가독성이 좋은 소스는 아닙니다.

오히려 주석이 너무 많아지게되면 소스 해독이 더 어려워 집니다. 그렇기 때문에 가독성을 위해서 이 열거형 타입은 매우 중요한 코딩 기법이라고 할 수 있습니다.


여기까지 C#에서 열거형(enum)을 사용하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.