ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 15장. 열거 타입과 비트 플래그
    CLR Via C# 2022. 2. 23. 13:52

    열거 타입

    열거 타입 enum은 기호 이름과 연결되는 값의 쌍을 묶어서 정의하는 타입이다. 기호의 이름에 연결된 숫자 값을 하드코딩된 숫자로 사용하는 대신, 열거 타입을 사용해야 하는 대에는 이유가 있다.

    • 열거 타입을 사용하면 프로그램을 좀 더 쓰고 읽고 관리하는 것을 쉽게 해준다. 또한 기호에 대응되는 숫자 값이 변하더라도 프로그램을 다시 컴파일하기만 하면 되므로 쉽게 값을 변경시킬 수 있다.
    • 열거 타입은 보통 강력한 타입으로 취급된다. 예를 들어 만약 Color.Orange 값을 Fruit 열거 타입을 매개변수로 받는 메서드에 전달하려고 하면 컴파일러가 오류가 있음을 알려준다.

    모든 열거 타입은 System.Enum 타입을 상속하는데, System.Enum은 System.ValueType을 상속하고, 최종적으로 System.Object을 상속한다. 그래서 열거 타입들은 값 타입으로 분류된다. 하지만 다른 값 타입과는 달리 열거 타입은 메서드, 속성, 이벤트 등을 정의할 수 없다. 그러나 C#의 확장 메서드 기능을 이용하면 열거 타입에 새로운 메서드를 추가하는 것과 유사한 동작을 흉내 낼 수 있다.

    열거 타입을 컴파일할 때, C# 컴파일러는 각각의 기호들을 타입 내의 상수 필드로 전환한다. 기본적으로 열거 타입은 다수의 상수 필드와 함께 하나의 인스턴스 필드를 가지고 있는 구조체다. 열거 타입에서 정의하고 있는 기호가 읽기 전용의 값이 아닌 상수이기 떄문에 버전 관리 문제를 신경 써줘야 한다.

    System.Enum 타입에는 GetUnderlyingType이라는 정적 메서드가 있고, System.Type 타입에는 인스턴스 메서드로 GetEnumUnderlyingType이라는 메서드가 있다.이 메서드들은 열거 타입의 값을 저장하고 있는 타입(내부 타입)을 반환한다.

    // System.Enum 타입에 정의되어 있다. 
    public static Type GetUnderlyingType(Type enumType);
    // System.Type 타입에 정의되어 있다. 
    public Type GetEnumUnderlyingType();

    또한 System.Enum의 정적 메서드인 GetValuesSystem.Type의 인스턴스 메서드인 GetEnumValues를 이용하면 열거 타입 내에서 정의하고 있는 개별 기호들을, 배열의 개별 요소로 저장하고 있는 배열을 가져올 수 있다.

    // System.Enum 타입에 정의되어 있다. 
    public static Array GetValues(Type enumType);
    // System.Type 타입에 정의되어 있다. 
    public Array GetEnumValues();

    기호를 얻어오는 메서드를 살펴보았으니 기호를 이용해 그에 해당하는 값을 얻는 메서드를 알아보자. 어떤 기호 이름을 열거 타입의 인스턴스로 변환하는 작업은 EnumParse 정적 메서드와 TryParse 정적 메서드를 통해서 쉽게 처리할 수 있다.

    마지막으로 Enum에는 IsDefined 정적 메서드와 TypeIsEnumDefined 인스턴스 메서드가 있다. 이를 이용하면 매개변수로 전달한 값이 특정 열거 타입에서 유효한 값인지를 판단할 수 있다. 하지만 이 IsDefined 메서드는 다음과 같은 문제들 때문에 주의해서 사용해야 한다.

    • 첫째, IsDefined 메서드는 항상 대소문자 구별을 하며, 대소문자를 구별하지 않을 방법이 없다.
    • 둘째, IsDefined 메서드는 내부적으로 리플렉션을 사용하므로 속도가 느리다.
    • 셋째, IsDefined 메서드는 이메서드를 호출하는 어셈블리 내부에 정의된 열거 타입에 대해서만 호출이 이루어져야만 한다. 이는 정의 후 나중에 열거 타입에 새로운 기호를 추가하면 잘못된 실행 결과를 가져올 가능성이 생긴다.

    비트 플래그

    System.IO.File 타입의 GetAttributes 메서드를 호출하면 이 메서드는 FileAttributes 타입의 인스턴스를 반환한다. FileAttributes 타입은 Int32 타입을 내부 타입으로 사용하는 열거 타입으로, 내부에는 파일의 개별 속성에 대한 정보를 반영하는 비트들이 설정되어 있다.

    [Flags, Serializable]
    public enum FileAttributes {
        ReadOly        = 0x00001,
        Hidden         = 0x00002,
        ...
        NoScrubData    = 0x20000
    }

    어떤 파일이 숨김 속성으로 지정되어 있는지 알아보려면 다음과 같이 코드를 작성할 수 있다.

    String file = Assembly.GetEntryAssembly().Location;
    FileAttributes attributes = File.GetAttributes(file);
    Console.WriteLine("Is {0} hidden {1}", file
                        , (attributes & FileAttributes.Hidden) != 0);

    그리고 다음의 코드는 파일을 읽기 전용 속성이면서 동시에 숨김 속성으로 변경하는 코드다.

    File.SetAttributes(file, FileAttributesReadOnly | FileAttributes.Hidden);

    열거 타입을 비트 플래그의 조합처럼 사용하려는 경우 반드시 System.FlagsAttribute 사용자 정의 특성을 지정해줘야 한다. [Flags] 또는 [FlagsAttribute]

    비트 플래그로 활용하기 위하여 선언했다고 하더라고, 열거 타입이므로 열거 타입이 사용가능한 메서드들도 당연히 활용할 수 있다. 예를 들어, [Flags] 사용자 정의 특성이 적용되어 있으면 ToString 메서드에 전달되는 매개변수가 단순히 고정된 값이 아니라 비트 플래그의 조합으로 나온 값이라는 것을 이해하고 그에 맞추어 처리해준다. 또한 [Flags] 사용자 정의 특성이 없어도 서식 문자열을 "F"로 지정하면 같은 결과를 얻을 수 있다.

    비트 플래그용 열거 타입에 대해서는 IsDefined 메서드를 절대 사용하지 말아야 하는데, 여기에는 두 가지 이유가 있다.

    • 만약 IsDefined에 문자열을 전달할 경우, 전달된 문자열을 검색하는 과정에서 토큰으로 분할을 수행하지 않기 때문에, 쉼표 기호가 들어있더라도 이를 무시한 채 문자열 전체를 비교하게 된다. 열거 타입에서는 기호 이름에 쉼표를 지정할 수 없으므로, 절대로 일치하는 기호를 찾을 수 없다.
    • 만약 IsDefined에 숫자 값을 전달할 경우 전달된 숫자와 정확하게 일치하는 값을 찾으려고 시도할 것이다. 비트 플래그의 경우 그렇지 않을 수 있으므로, 이 경우 보통 false를 반환하게 된다.

    'CLR Via C#' 카테고리의 다른 글

    17장. 델리게이트  (0) 2022.03.09
    16장. 배열  (0) 2022.02.28
    14장. 문자, 문자열, 텍스트 사용하기  (0) 2022.02.21
    13장. 인터페이스  (0) 2022.02.16
    12장. 제네릭(Generics)  (0) 2022.02.12
Designed by Tistory.