C #에서 int가 적법한 열거 형인지 확인하는 방법이 있습니까?


167

SO 게시물을 읽었으며 가장 기본적인 작업이 누락 된 것 같습니다.

public enum LoggingLevel
{
    Off = 0,
    Error = 1,
    Warning = 2,
    Info = 3,
    Debug = 4,
    Trace = 5
};

if (s == "LogLevel")
{
    _log.LogLevel = (LoggingLevel)Convert.ToInt32("78");
    _log.LogLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), "78");
    _log.WriteDebug(_log.LogLevel.ToString());
}

예외가 발생하지 않으므로 기꺼이 보관하십시오 78. 열거 형에 들어가는 값을 확인하는 방법이 있습니까?


2
가능한 열거 형 값의
Erik

답변:


271

열거 형을 확인하십시오.

용법:

if(Enum.IsDefined(typeof(MyEnum), value))
    MyEnum a = (MyEnum)value; 

다음은 해당 페이지의 예입니다.

using System;    
[Flags] public enum PetType
{
   None = 0, Dog = 1, Cat = 2, Rodent = 4, Bird = 8, Reptile = 16, Other = 32
};

public class Example
{
   public static void Main()
   {
      object value;     
      // Call IsDefined with underlying integral value of member.
      value = 1;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with invalid underlying integral value.
      value = 64;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with string containing member name.
      value = "Rodent";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with a variable of type PetType.
      value = PetType.Dog;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = PetType.Dog | PetType.Cat;
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with uppercase member name.      
      value = "None";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = "NONE";
      Console.WriteLine("{0}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      // Call IsDefined with combined value
      value = PetType.Dog | PetType.Bird;
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
      value = value.ToString();
      Console.WriteLine("{0:D}: {1}", value, Enum.IsDefined(typeof(PetType), value));
   }
}

예제는 다음 출력을 표시합니다.

//       1: True
//       64: False
//       Rodent: True
//       Dog: True
//       Dog, Cat: False
//       None: True
//       NONE: False
//       9: False
//       Dog, Bird: False

@matti : "78"을 LoggingLevel저장으로 사용 하는 숫자 표현으로 변환 한 다음 LoggingLevel열거 형 값으로 표시합니다.
thecoop

9
IsDefined비트 단위 열거 형 멤버에는 작동하지 않는 것 같습니다 .
Saeed Neamati

29

위의 솔루션은 [Flags]상황을 처리하지 않습니다 .

아래의 솔루션에는 성능 문제가있을 수 있지만 (다양한 방법으로 최적화 할 수 있다고 확신합니다) 본질적으로 열거 형 값이 유효한지 여부를 항상 증명합니다 .

세 가지 가정에 의존합니다.

  • C #에서 열거 값은 할 수 없습니다 int다른 절대적으로 아무것도,
  • C #의 열거 형 이름 은 알파벳 문자로 시작 해야 합니다
  • 빼기 부호와 함께 유효한 열거 이름을 사용할 수 없습니다. -

ToString()열거 형을 호출 하면 열거 int형 (플래그 여부)이 일치하지 않으면 값이 반환됩니다 . 허용 된 열거 형 값이 일치하면 일치하는 이름이 인쇄됩니다.

그래서:

[Flags]
enum WithFlags
{
    First = 1,
    Second = 2,
    Third = 4,
    Fourth = 8
}

((WithFlags)2).ToString() ==> "Second"
((WithFlags)(2 + 4)).ToString() ==> "Second, Third"
((WithFlags)20).ToString() ==> "20"

이 두 가지 규칙을 염두에두고 .NET Framework가 올바르게 작업을 수행하면 유효한 열거 형 ToString()메서드를 호출 하면 알파벳 문자가 첫 문자로 표시됩니다.

public static bool IsValid<TEnum>(this TEnum enumValue)
    where TEnum : struct
{
    var firstChar = enumValue.ToString()[0];
    return (firstChar < '0' || firstChar > '9') && firstChar != '-';
}

이를 "해킹"이라고 부를 수는 있지만 Microsoft의 자체 구현 Enum및 C # 표준 에 의존함으로써 잠재적으로 버그가있는 코드 나 검사에 의존하지 않는다는 장점이 있습니다. 성능이 그다지 중요하지 않은 상황에서는 많은 불쾌한 switch진술이나 다른 점검 사항 이 저장됩니다 !

편집하다

내 원래 구현이 음수 값을 지원하지 않음을 지적한 @ChaseMedallion에게 감사드립니다. 이 문제는 해결되고 테스트되었습니다.

그리고 그것을 테스트하는 테스트 :

[TestClass]
public class EnumExtensionsTests
{
    [Flags]
    enum WithFlags
    {
        First = 1,
        Second = 2,
        Third = 4,
        Fourth = 8
    }

    enum WithoutFlags
    {
        First = 1,
        Second = 22,
        Third = 55,
        Fourth = 13,
        Fifth = 127
    }

    enum WithoutNumbers
    {
        First, // 1
        Second, // 2
        Third, // 3
        Fourth // 4
    }

    enum WithoutFirstNumberAssigned
    {
        First = 7,
        Second, // 8
        Third, // 9
        Fourth // 10
    }


    enum WithNagativeNumbers
    {
        First = -7,
        Second = -8,
        Third = -9,
        Fourth = -10
    }

    [TestMethod]
    public void IsValidEnumTests()
    {
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4)).IsValid());
        Assert.IsTrue(((WithFlags)(1 | 4 | 2)).IsValid());
        Assert.IsTrue(((WithFlags)(2)).IsValid());
        Assert.IsTrue(((WithFlags)(3)).IsValid());
        Assert.IsTrue(((WithFlags)(1 + 2 + 4 + 8)).IsValid());

        Assert.IsFalse(((WithFlags)(16)).IsValid());
        Assert.IsFalse(((WithFlags)(17)).IsValid());
        Assert.IsFalse(((WithFlags)(18)).IsValid());
        Assert.IsFalse(((WithFlags)(0)).IsValid());

        Assert.IsTrue(((WithoutFlags)1).IsValid());
        Assert.IsTrue(((WithoutFlags)22).IsValid());
        Assert.IsTrue(((WithoutFlags)(53 | 6)).IsValid());   // Will end up being Third
        Assert.IsTrue(((WithoutFlags)(22 | 25 | 99)).IsValid()); // Will end up being Fifth
        Assert.IsTrue(((WithoutFlags)55).IsValid());
        Assert.IsTrue(((WithoutFlags)127).IsValid());

        Assert.IsFalse(((WithoutFlags)48).IsValid());
        Assert.IsFalse(((WithoutFlags)50).IsValid());
        Assert.IsFalse(((WithoutFlags)(1 | 22)).IsValid());
        Assert.IsFalse(((WithoutFlags)(9 | 27 | 4)).IsValid());

        Assert.IsTrue(((WithoutNumbers)0).IsValid());
        Assert.IsTrue(((WithoutNumbers)1).IsValid());
        Assert.IsTrue(((WithoutNumbers)2).IsValid());
        Assert.IsTrue(((WithoutNumbers)3).IsValid());
        Assert.IsTrue(((WithoutNumbers)(1 | 2)).IsValid()); // Will end up being Third
        Assert.IsTrue(((WithoutNumbers)(1 + 2)).IsValid()); // Will end up being Third

        Assert.IsFalse(((WithoutNumbers)4).IsValid());
        Assert.IsFalse(((WithoutNumbers)5).IsValid());
        Assert.IsFalse(((WithoutNumbers)25).IsValid());
        Assert.IsFalse(((WithoutNumbers)(1 + 2 + 3)).IsValid());

        Assert.IsTrue(((WithoutFirstNumberAssigned)7).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)8).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)9).IsValid());
        Assert.IsTrue(((WithoutFirstNumberAssigned)10).IsValid());

        Assert.IsFalse(((WithoutFirstNumberAssigned)11).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)6).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(7 | 9)).IsValid());
        Assert.IsFalse(((WithoutFirstNumberAssigned)(8 + 10)).IsValid());

        Assert.IsTrue(((WithNagativeNumbers)(-7)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-8)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-9)).IsValid());
        Assert.IsTrue(((WithNagativeNumbers)(-10)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(-11)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(7)).IsValid());
        Assert.IsFalse(((WithNagativeNumbers)(8)).IsValid());
    }
}

1
이 덕분에 유효한 플래그 조합을 다루는 비슷한 문제가 발생했습니다. 열거 형의 첫 번째 문자를 확인하는 대신 int.TryParse (enumValue.ToString ()) ... 시도 할 수도 있습니다. 실패하면 유효한 플래그 집합이 있습니다. 그러나 실제로 솔루션보다 느릴 수 있습니다.
MadHenchbot

이 구현은 숫자가 아닌 문자를 검사하기 때문에 음수 값의 유효성을 올바르게 검사하지 못합니다.
ChaseMedallion

잘 잡아! 감사합니다. @ChaseMedallion
joshcomley

나는이 솔루션이 가장 좋으며, 제시된 수학 트릭 [Flags]은 정수 값이 합리적인 경우에만 작동 합니다.
MrLore

17

정식 답변은 Enum.IsDefined이지만 a : 타이트 루프에서 사용하면 약간 느리고 b : [Flags]열거 형 에는 유용하지 않습니다 .

개인적으로 나는 그것에 대해 걱정하지 않고 switch적절하게 기억합니다.

  • 모든 것을 인식하지 못하고 (아무것도하지 않으면) 괜찮 으면 추가하지 마십시오 default:(또는 default:이유를 설명 하지 않는 빈 내용이 있음)
  • 합리적인 기본 행동이 있다면, default:
  • 그렇지 않으면 알고있는 것을 처리하고 나머지는 예외로 처리하십시오.

이렇게 :

switch(someflag) {
    case TriBool.Yes:
        DoSomething();
        break;
    case TriBool.No:
        DoSomethingElse();
        break;
    case TriBool.FileNotFound:
        DoSomethingOther();
        break;
    default:
        throw new ArgumentOutOfRangeException("someflag");
}

[Flags] 열거 형에 익숙하지 않으며 성능은 문제가되지 않으므로 귀하의 답변은 열거 형이 처음에 발명 된 이유처럼 보입니다. . 아무것도 얻지 못했지만 한 열거 형 정의에 257 개의 값이있는 구성 파일을 읽는 상황에 대해 생각하십시오. 수십 개의 다른 열거 형은 물론입니다. 많은 행이있을 것입니다 ...
char m

@matti-극단적 인 예를 들었습니다. 직렬화 해제는 어쨌든 전문가 영역입니다. 대부분의 직렬화 엔진은 무료로 열거 형 유효성 검사를 제공합니다.
Marc Gravell

@matti-사이드 노트; 개인의 장점을 바탕으로 답변을 처리한다고 말하고 싶습니다. 때때로 문제가 완전히 풀리고 "rep 17"을 가진 사람도 마찬가지로 완벽한 답을 줄 수 있습니다 .
Marc Gravell

스위치 답변은 빠르지 만 일반적이지 않습니다.
Eldritch Conundrum

8

사용하다:

Enum.IsDefined ( typeof ( Enum ), EnumValue );


4

에 대처하기 위해 [Flags]또한 사용 할 수 있습니다 C # 요리 책에서이 솔루션을 :

먼저 ALL열거 형에 새 값을 추가 하십시오.

[Flags]
enum Language
{
    CSharp = 1, VBNET = 2, VB6 = 4, 
    All = (CSharp | VBNET | VB6)
}

그런 다음 값이 다음에 있는지 확인하십시오 ALL.

public bool HandleFlagsEnum(Language language)
{
    if ((language & Language.All) == language)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}

2

한 가지 방법은 캐스팅과 열거 형을 문자열로 변환하는 것입니다. int를 Enum 유형으로 캐스팅 할 때 int는 해당 enum 값으로 변환되거나 열거 형 값이 int에 대해 정의되지 않은 경우 결과 enum에 int가 값으로 포함됩니다.

enum NetworkStatus{
  Unknown=0,
  Active,
  Slow
}

int statusCode=2;
NetworkStatus netStatus = (NetworkStatus) statusCode;
bool isDefined = netStatus.ToString() != statusCode.ToString();

에지 케이스에 대해서는 테스트되지 않았습니다.


1

다른 사람들이 말했듯 이으로 장식 된 열거 형에 대해 비트 플래그의 유효한 조합이 있더라도 Enum.IsDefined반환 false합니다 FlagsAttribute.

안타깝게도 유효한 비트 플래그에 대해 true를 반환하는 메서드를 만드는 유일한 방법 은 약간 길다

public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    // For enums decorated with the FlagsAttribute, allow sets of flags.
    if (!valid && enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true)
    {
        long mask = 0;
        foreach (object definedValue in Enum.GetValues(enumType))
            mask |= Convert.ToInt64(definedValue);
        long longValue = Convert.ToInt64(value);
        valid = (mask & longValue) == longValue;
    }
    return valid;
}

GetCustomAttribute사전에 결과를 캐시 할 수 있습니다 .

private static readonly Dictionary<Type, bool> _flagEnums = new Dictionary<Type, bool>();
public static bool ValidateEnumValue<T>(T value) where T : Enum
{
    // Check if a simple value is defined in the enum.
    Type enumType = typeof(T);
    bool valid = Enum.IsDefined(enumType, value);
    if (!valid)
    {
        // For enums decorated with the FlagsAttribute, allow sets of flags.
        if (!_flagEnums.TryGetValue(enumType, out bool isFlag))
        {
            isFlag = enumType.GetCustomAttributes(typeof(FlagsAttribute), false)?.Any() == true;
            _flagEnums.Add(enumType, isFlag);
        }
        if (isFlag)
        {
            long mask = 0;
            foreach (object definedValue in Enum.GetValues(enumType))
                mask |= Convert.ToInt64(definedValue);
            long longValue = Convert.ToInt64(value);
            valid = (mask & longValue) == longValue;
        }
    }
    return valid;
}

위의 코드는 C # 7.3 이후에만 사용할 수 있는 새로운 Enum제약 조건을 사용합니다 T. object value이전 버전으로 를 전달 하고 호출 GetType()해야합니다.

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