int를 잘못된 enum 값으로 캐스팅하면 예외가 발생하지 않는 이유는 무엇입니까?


124

다음과 같은 열거 형이있는 경우 :

enum Beer
{
    Bud = 10,
    Stella = 20,
    Unknown
}

int이 값을 벗어난을 유형으로 캐스팅 할 때 왜 예외가 발생하지 Beer않습니까?

예를 들어 다음 코드는 예외를 throw하지 않고 콘솔에 '50'을 출력합니다.

int i = 50;
var b = (Beer) i;

Console.WriteLine(b.ToString());

나는 이것이 이상하다는 것을 알았다. 누구든지 명확히 할 수 있는가?


27
Enum.IsDefined를 사용 하여 값이 유효한지 항상 확인할 수 있습니다.
Tim Schmelter 2011 년

난 몰랐어 시원 아마 내가 현재 해결하기 위해 노력하고 문제의 사이트를 사용할 것
jcvandan

4
하지만 읽어
보세요

1
Stella를 Bud보다 두 배 더 가치있게 만드는 것에 대해 찬성했습니다.
Dan Bechard 2017-06-05

답변:


81

에서 촬영 열거 구문 분석과 혼란

이것은 .NET을 만든 사람들의 결정이었습니다. 열거가 (다른 값 유형에 의해 백업됩니다 int, short,byte 실제로 그 값 유형에 대한 유효 값을 가질 수 있도록, 등)합니다.

나는 개인적으로 이것이 작동하는 방식을 좋아하지 않으므로 일련의 유틸리티 방법을 만들었습니다.

/// <summary>
/// Utility methods for enum values. This static type will fail to initialize 
/// (throwing a <see cref="TypeInitializationException"/>) if
/// you try to provide a value that is not an enum.
/// </summary>
/// <typeparam name="T">An enum type. </typeparam>
public static class EnumUtil<T>
    where T : struct, IConvertible // Try to get as much of a static check as we can.
{
    // The .NET framework doesn't provide a compile-checked
    // way to ensure that a type is an enum, so we have to check when the type
    // is statically invoked.
    static EnumUtil()
    {
        // Throw Exception on static initialization if the given type isn't an enum.
        Require.That(typeof (T).IsEnum, () => typeof(T).FullName + " is not an enum type.");
    }

    /// <summary>
    /// In the .NET Framework, objects can be cast to enum values which are not
    /// defined for their type. This method provides a simple fail-fast check
    /// that the enum value is defined, and creates a cast at the same time.
    /// Cast the given value as the given enum type.
    /// Throw an exception if the value is not defined for the given enum type.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumValue"></param>
    /// <exception cref="InvalidCastException">
    /// If the given value is not a defined value of the enum type.
    /// </exception>
    /// <returns></returns>
    public static T DefinedCast(object enumValue)

    {
        if (!System.Enum.IsDefined(typeof(T), enumValue))
            throw new InvalidCastException(enumValue + " is not a defined value for enum type " +
                                           typeof (T).FullName);
        return (T) enumValue;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="enumValue"></param>
    /// <returns></returns>
    public static T Parse(string enumValue)
    {
        var parsedValue = (T)System.Enum.Parse(typeof (T), enumValue);
        //Require that the parsed value is defined
        Require.That(parsedValue.IsDefined(), 
            () => new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", 
                enumValue, typeof(T).FullName)));
        return parsedValue;
    }

    public static bool IsDefined(T enumValue)
    {
        return System.Enum.IsDefined(typeof (T), enumValue);
    }

}


public static class EnumExtensions
{
    public static bool IsDefined<T>(this T enumValue)
        where T : struct, IConvertible
    {
        return EnumUtil<T>.IsDefined(enumValue);
    }
}

이렇게 말할 수 있습니다.

if(!sEnum.IsDefined()) throw new Exception(...);

... 또는 :

EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.

편집하다

위에 제공된 설명 외에도 Enum의 .NET 버전은 Java에서 영감을받은 패턴보다 C에서 영감을받은 패턴을 따릅니다. 이렇게하면 이진 패턴을 사용하여 특정 "플래그"가 열거 형 값에서 활성 상태인지 여부를 확인할 수 있는 "비트 플래그"열거 형 을 가질 수 있습니다. 가능한 모든 플래그 조합 (예 : MondayAndTuesday, MondayAndWednesdayAndThursday) 을 정의해야한다면 매우 지루할 것입니다. 따라서 정의되지 않은 열거 형 값을 사용할 수있는 능력이 있으면 정말 편리 할 수 ​​있습니다. 이러한 종류의 트릭을 활용하지 않는 enum 유형에 대한 빠른 실패 동작을 원할 때 약간의 추가 작업이 필요합니다.


니스 ... 내가 어느 작동하는 방식의 팬이 아니에요, 나에게 이상한 것 같다
jcvandan

3
@dormisher : " '이건 광기인데도 안 할 방법이 있어요." 내 편집을 참조하십시오.
StriplingWarrior

2
@StriplingWarrior, 관심을. Java에 해당하는 것은 EnumSet.of (월요일, 수요일, 목요일)입니다. EnumSet 내부에는 하나의 long이 있습니다. 따라서 많은 효율성 손실없이 멋진 API를 제공합니다.
Iain


확장 메서드처럼 IsDefined를 호출하려는 구문 분석 메서드의 @StriplingWarrior. 그러나 IsDefined 메서드는 제약 조건이있는 EnumUtil 클래스를 만들려고하는 방식 때문에 확장 메서드로 만들 수 없습니다 (매우 좋아하는 enum에 대한 정적 검사를 수행하기 위해). 그러나 실제로 그것은 제네릭 클래스에서 확장 메서드를 만들 수 없다는 것을 알려주는 것입니다. 어떤 아이디어?
CJC

55

열거 형은 종종 플래그로 사용됩니다.

[Flags]
enum Permission
{
    None = 0x00,
    Read = 0x01,
    Write = 0x02,
}
...

Permission p = Permission.Read | Permission.Write;

p의 값은 정수 3이며 열거 형의 값은 아니지만 유효한 값입니다.

나는 개인적으로 다른 해결책을 보았을 것입니다. 나는 "비트 배열"정수 유형 "고유 한 값의 세트"유형을 둘 다 "열거 형"으로 병합하는 것보다 두 가지 다른 언어 기능으로 만드는 기능을 갖고 싶었습니다. 하지만 이것이 원래 언어와 프레임 워크 디자이너가 생각 해낸 것입니다. 결과적으로 우리는 선언되지 않은 열거 형 값을 합법적 인 값으로 허용해야합니다.


19
그러나 3 "분명히 유효한 가치"는 OP의 요점을 강화합니다. 이 [Flags] 속성은 정의 된 열거 형 또는 유효한 값을 찾도록 컴파일러에 지시 할 수 있습니다. 그냥 말해.
LarsTech 2011-06-20

15

짧은 대답 : 언어 디자이너는 이러한 방식으로 언어를 디자인하기로 결정했습니다.

Section 6.2.2: Explicit enumeration conversionsC # 언어 사양 의 긴 대답은 다음 과 같습니다.

두 유형 간의 명시 적 열거 변환은 참여하는 열거 형 유형을 해당 열거 형 유형의 기본 유형으로 처리 한 다음 결과 유형간에 암시 적 또는 명시 적 숫자 변환을 수행하여 처리됩니다. 예를 들어, 기본 유형이 int 인 enum 유형 E가있는 경우 E에서 바이트로의 변환은 int에서 바이트로의 명시 적 숫자 변환 (§6.2.1)으로 처리되고 바이트에서 E 로의 변환은 다음과 같이 처리됩니다. byte에서 int 로의 암시 적 숫자 변환 (§6.1.2)

기본적으로 enum 은 변환 작업을 수행 할 때 기본 유형으로 처리됩니다. 기본적으로 enum 의 기본 유형은 Int32입니다. 즉, 변환이 로의 변환과 똑같이 처리됩니다 Int32. 이는 모든 유효한 int값이 허용됨을 의미합니다 .

나는 이것이 주로 성능상의 이유로 수행되었다고 생각합니다. enum간단한 정수 형식 을 만들고 정수 형식 변환을 허용하면 CLR이 모든 추가 검사를 수행 할 필요가 없습니다. 이는 enum정수를 사용하는 것과 비교할 때 an을 사용하는 것이 실제로 성능 손실 이 없음을 의미하므로 사용 을 장려하는 데 도움이됩니다.


이상하다고 생각하십니까? 열거 형의 요점 중 하나는 개별 의미를 부여 할 수있는 정수 범위를 안전하게 그룹화하는 것이라고 생각했습니다. 나에게 열거 형 유형이 정수 값을 보유하도록 허용하는 것은 그 목적에 맞지 않습니다.
jcvandan 2011-06-20

@dormisher : 방금 편집하고 끝에 약간 추가했습니다. 당신이 추구하는 "안전"을 제공하는 데는 비용이 듭니다. 즉, 정상적인 사용에서는 실제로 전혀 문제가되지 않습니다.
Reed Copsey 2011 년

9

로부터 문서 :

Days 유형의 변수에는 기본 유형 범위의 모든 값을 지정할 수 있습니다. 값은 명명 된 상수로 제한되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.