문제

유효한 열거형 값인지 확인하려면 정수의 유효성을 검사해야 합니다.

C#에서 이 작업을 수행하는 가장 좋은 방법은 무엇입니까?

도움이 되었습니까?

해결책

데이터가 항상 UI에서 나올 뿐만 아니라 사용자가 제어할 수 있는 UI에서 나온다고 가정하는 사람들을 좋아하게 될 것입니다!

IsDefined 대부분의 시나리오에서는 괜찮습니다. 다음과 같이 시작할 수 있습니다.

public static bool TryParseEnum<TEnum>(this int enumValue, out TEnum retVal)
{
 retVal = default(TEnum);
 bool success = Enum.IsDefined(typeof(TEnum), enumValue);
 if (success)
 {
  retVal = (TEnum)Enum.ToObject(typeof(TEnum), enumValue);
 }
 return success;
}

(분명히 적절한 int 확장이 아니라고 생각한다면 'this'를 삭제하세요)

다른 팁

IMHO 답변으로 표시된 게시물이 올바르지 않습니다.
매개변수와 데이터 검증은 수십 년 전에 제가 배운 것 중 하나입니다.

기본적으로 오류 없이 어떤 정수 값이라도 열거형에 할당할 수 있으므로 유효성 검사가 필요합니다.
많은 경우에 필요한 기능이기 때문에 C# 열거형 유효성 검사를 연구하는 데 많은 시간을 보냈습니다.

어디

열거형 유효성 검사의 주요 목적은 파일에서 읽은 데이터의 유효성을 검사하는 것입니다.파일이 손상되었는지, 외부에서 수정되었는지, 의도적으로 해킹되었는지 알 수 없습니다.
그리고 클립보드에서 붙여넣은 애플리케이션 데이터의 열거형 검증을 사용하면 다음과 같습니다.사용자가 클립보드 내용을 편집했는지 여부는 알 수 없습니다.

즉, 나는 내가 찾거나 디자인할 수 있는 모든 방법의 성능을 프로파일링하는 것을 포함하여 다양한 방법을 연구하고 테스트하는 데 며칠을 보냈습니다.

System.Enum에서 무엇이든 호출하는 것은 너무 느려서 범위에 대해 유효성을 검사해야 하는 속성에 하나 이상의 열거형이 있는 수백 또는 수천 개의 개체가 포함된 함수에 대해 눈에 띄는 성능 저하가 있었습니다.

결론적으로, 멀리하세요 모든 것 System.Enum 클래스에서 열거형 값의 유효성을 검사할 때 속도가 매우 느립니다.

결과

내가 현재 열거형 유효성 검사에 사용하는 방법은 아마도 많은 프로그래머들의 눈길을 끌 것입니다. 그러나 그것은 내 특정 응용 프로그램 디자인에 있어서 가장 덜 해롭습니다.

열거형의 상한 및 (선택적으로) 하한인 하나 또는 두 개의 상수를 정의하고 유효성 검사를 위해 한 쌍의 if() 문에 사용합니다.
한 가지 단점은 열거형을 변경하는 경우 상수를 업데이트해야 한다는 것입니다.
이 방법은 열거형이 각 열거형 요소가 0,1,2,3,4,...와 같은 증분 정수 값인 "자동" 스타일인 경우에만 작동합니다.증분 값이 아닌 플래그 또는 열거형에서는 제대로 작동하지 않습니다.

또한 이 방법은 일반 int32에서 "<" ">"인 경우(내 테스트에서 38,000틱을 기록함) 일반 방법만큼 빠릅니다.

예를 들어:

public const MyEnum MYENUM_MINIMUM = MyEnum.One;
public const MyEnum MYENUM_MAXIMUM = MyEnum.Four;

public enum MyEnum
{
    One,
    Two,
    Three,
    Four
};

public static MyEnum Validate(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

성능

관심 있는 분들을 위해 열거형 유효성 검사에 대한 다음 변형을 프로파일링했으며 그 결과는 다음과 같습니다.

프로파일링은 임의의 정수 입력 값을 사용하여 각 메서드에 대해 백만 번 루프로 릴리스 컴파일 시 수행되었습니다.각 테스트는 10회 이상 실행되었으며 평균이 나왔습니다.틱 결과에는 난수 생성 등을 포함하는 총 실행 시간이 포함됩니다.하지만 이는 테스트 전반에 걸쳐 일정합니다.1틱 = 10ns.

여기의 코드는 완전한 테스트 코드가 아니며 기본적인 열거형 유효성 검사 방법일 뿐입니다.또한 테스트된 이들에 대한 추가 변형이 많이 있었으며 모두 여기에 표시된 1,800,000틱 벤치마크와 유사한 결과를 보였습니다.

반올림된 결과로 가장 느린 것부터 가장 빠른 것까지 나열됩니다. 오타가 없기를 바랍니다.

방법에서 결정된 경계 = 13,600,000틱

public static T Clamp<T>(T value)
{
    int minimum = Enum.GetValues(typeof(T)).GetLowerBound(0);
    int maximum = Enum.GetValues(typeof(T)).GetUpperBound(0);

    if (Convert.ToInt32(value) < minimum) { return (T)Enum.ToObject(typeof(T), minimum); }
    if (Convert.ToInt32(value) > maximum) { return (T)Enum.ToObject(typeof(T), maximum); }
    return value;
}

Enum.IsDefined = 1,800,000틱
메모:이 코드 버전은 최소/최대로 고정되지 않지만 범위를 벗어나면 기본값을 반환합니다.

public static T ValidateItem<T>(T eEnumItem)
{
    if (Enum.IsDefined(typeof(T), eEnumItem) == true)
        return eEnumItem;
    else
        return default(T);
}

System.Enum 캐스트를 사용하여 Int32 변환 = 1,800,000틱

public static Enum Clamp(this Enum value, Enum minimum, Enum maximum)
{
    if (Convert.ToInt32(value) < Convert.ToInt32(minimum)) { return minimum; }
    if (Convert.ToInt32(value) > Convert.ToInt32(maximum)) { return maximum; }
    return value;
}

if() 최소/최대 상수 = 43,000틱 = 42배, 316배 빠른 승자.

public static MyEnum Clamp(MyEnum value)
{
    if (value < MYENUM_MINIMUM) { return MYENUM_MINIMUM; }
    if (value > MYENUM_MAXIMUM) { return MYENUM_MAXIMUM; }
    return value;
}

-얼-

다른 사람들이 언급했듯이, Enum.IsDefined 느리므로 루프에 있는 경우 주의해야 할 사항입니다.

다중 비교를 수행할 때 더 빠른 방법은 먼저 값을 HashSet.그런 다음 간단히 사용하십시오. Contains 다음과 같이 값이 유효한지 확인합니다.

int userInput = 4;
// below, Enum.GetValues converts enum to array. We then convert the array to hashset.
HashSet<int> validVals = new HashSet<int>((int[])Enum.GetValues(typeof(MyEnum)));
// the following could be in a loop, or do multiple comparisons, etc.
if (validVals.Contains(userInput))
{
    // is valid
}

브래드 에이브럼스(Brad Abrams)는 다음과 같이 구체적으로 경고합니다. Enum.IsDefined 그의 게시물에서 지나친 단순화의 위험.

이 요구 사항(즉, 열거형의 유효성을 검사해야 하는 필요성)을 제거하는 가장 좋은 방법은 사용자가 오류를 범할 수 있는 방법(예: 일종의 입력 상자)을 제거하는 것입니다.예를 들어 유효한 열거형만 적용하려면 드롭다운이 있는 열거형을 사용하세요.

이 답변은 System.Enum의 성능 문제를 제기하는 deegee의 답변에 대한 응답이므로 내가 선호하는 일반적인 답변으로 간주해서는 안 되며, 엄격한 성능 시나리오에서 열거형 유효성 검사를 더 많이 다루어야 합니다.

느리지만 기능적인 코드가 긴밀한 루프에서 실행되는 미션 크리티컬 성능 문제가 있는 경우 개인적으로 기능을 줄여 해결하는 대신 가능하면 해당 코드를 루프 밖으로 이동하는 방법을 살펴보겠습니다.연속된 열거형만 지원하도록 코드를 제한하는 것은 예를 들어 미래에 누군가가 일부 열거형 값을 더 이상 사용하지 않기로 결정한 경우 버그를 찾는 악몽이 될 수 있습니다.간단하게는 Enum.GetValues를 처음부터 한 번만 호출하면 모든 반사 등을 수천 번 트리거하는 것을 방지할 수 있습니다.그러면 즉각적인 성능 향상을 얻을 수 있습니다.더 많은 성능이 필요하고 많은 열거형이 연속되어 있다는 것을 알고 있다면(하지만 여전히 '간격이 있는' 열거형을 지원하고 싶은 경우) 한 단계 더 나아가 다음과 같은 작업을 수행할 수 있습니다.

public abstract class EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    protected static bool IsContiguous
    {
        get
        {
            int[] enumVals = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();

            int lowest = enumVals.OrderBy(i => i).First();
            int highest = enumVals.OrderByDescending(i => i).First();

            return !Enumerable.Range(lowest, highest).Except(enumVals).Any();
        }
    }

    public static EnumValidator<TEnum> Create()
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("Please use an enum!");
        }

        return IsContiguous ? (EnumValidator<TEnum>)new ContiguousEnumValidator<TEnum>() : new JumbledEnumValidator<TEnum>();
    }

    public abstract bool IsValid(int value);
}

public class JumbledEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int[] _values;

    public JumbledEnumValidator()
    {
        _values = Enum.GetValues(typeof (TEnum)).Cast<int>().ToArray();
    }

    public override bool IsValid(int value)
    {
        return _values.Contains(value);
    }
}

public class ContiguousEnumValidator<TEnum> : EnumValidator<TEnum> where TEnum : struct, IConvertible
{
    private readonly int _highest;
    private readonly int _lowest;

    public ContiguousEnumValidator()
    {
        List<int> enumVals = Enum.GetValues(typeof (TEnum)).Cast<int>().ToList();

        _lowest = enumVals.OrderBy(i => i).First();
        _highest = enumVals.OrderByDescending(i => i).First();
    }

    public override bool IsValid(int value)
    {
        return value >= _lowest && value <= _highest;
    }
}

루프가 다음과 같이 되는 경우:

//Pre import-loop
EnumValidator< MyEnum > enumValidator = EnumValidator< MyEnum >.Create();
while(import)   //Tight RT loop.
{
    bool isValid = enumValidator.IsValid(theValue);
}

EnumValidator 클래스가 더 효율적으로 작성될 수 있다고 확신합니다(시연하기 위한 빠른 해킹일 뿐입니다). 솔직히 말해서 가져오기 루프 외부에서 무슨 일이 일어나는지 누가 신경 쓰나요?초고속이 필요한 유일한 비트는 루프 내에 있습니다.이것이 루프에서 불필요한 if-enumContiguous-then-else를 피하기 위해 추상 클래스 경로를 사용하는 이유입니다(Create 팩토리는 기본적으로 이 작업을 미리 수행합니다).간결함을 위해 이 코드는 기능을 int-enum으로 제한하므로 약간의 위선이 있음을 알 수 있습니다.int를 직접 사용하는 대신 IConvertible을 사용해야 하지만 이 답변은 이미 장황합니다!

이것이 온라인의 여러 게시물을 기반으로 수행하는 방법입니다.이렇게 하는 이유는 다음과 같이 표시된 열거형을 확인하기 위한 것입니다. Flags 속성의 유효성도 성공적으로 확인할 수 있습니다.

public static TEnum ParseEnum<TEnum>(string valueString, string parameterName = null)
{
    var parsed = (TEnum)Enum.Parse(typeof(TEnum), valueString, true);
    decimal d;
    if (!decimal.TryParse(parsed.ToString(), out d))
    {
        return parsed;
    }

    if (!string.IsNullOrEmpty(parameterName))
    {
        throw new ArgumentException(string.Format("Bad parameter value. Name: {0}, value: {1}", parameterName, valueString), parameterName);
    }
    else
    {
        throw new ArgumentException("Bad value. Value: " + valueString);
    }
}

나는 이것을 찾았다 링크 그것은 아주 잘 대답합니다.다음을 사용합니다:

(ENUMTYPE)Enum.ToObject(typeof(ENUMTYPE), INT)

저는 성능, 편의성, 유지 관리 용이성을 모두 고려하여 이 솔루션을 선택했습니다.

public enum DogBreed
{
    Unknown = 0,
    Beagle = 1,
    Labrador = 2,
    PeruvianIncaOrchid = 3,
}
public static class DogBreedExtensions
{
    public static bool IsValidDogBreed(this DogBreed breed)
    {
        var v = (int)breed;
        return v >= 1 && v <= 3;
    }
}

그리고 다음과 같이 사용하세요:

var goodInput = 2;
var goodDog = (DogBreed)goodInput;
goodDog.IsValidDogBreed(); // true.

var badInput = 7;
var badDog = (DogBreed)badInput; // no problem, bad data here we come.
badDog.IsValidDogBreed(); // false, you're in the doghouse!

내 경우에는 일반적으로 "알 수 없음" 열거형 값을 거부하려고 하기 때문에 Enum.IsDefined를 호출하면 절반만 얻을 수 있습니다.

이 접근 방식은 Enum.IsDefined의 성능 문제를 피하고 내가 좋아하는 열거형에 가까운 유효성 검사 규칙을 정의합니다.

확장 방법은 변화하는 요구 사항에 쉽게 적응할 수 있으며 원하는 경우 int에 적용할 수 있습니다. public static bool IsValidDogBreed(this int breed))

다음은 정적으로 구성된을 사용하는 빠른 일반 솔루션입니다. HashSet<T>.

도구 상자에서 이를 한 번 정의한 다음 모든 열거형 유효성 검사에 사용할 수 있습니다.

public static class EnumHelpers
{
    /// <summary>
    /// Returns whether the given enum value is a defined value for its type.
    /// Throws if the type parameter is not an enum type.
    /// </summary>
    public static bool IsDefined<T>(T enumValue)
    {
        if (typeof(T).BaseType != typeof(System.Enum)) throw new ArgumentException($"{nameof(T)} must be an enum type.");

        return EnumValueCache<T>.DefinedValues.Contains(enumValue);
    }

    /// <summary>
    /// Statically caches each defined value for each enum type for which this class is accessed.
    /// Uses the fact that static things exist separately for each distinct type parameter.
    /// </summary>
    internal static class EnumValueCache<T>
    {
        public static HashSet<T> DefinedValues { get; }

        static EnumValueCache()
        {
            if (typeof(T).BaseType != typeof(System.Enum)) throw new Exception($"{nameof(T)} must be an enum type.");

            DefinedValues = new HashSet<T>((T[])System.Enum.GetValues(typeof(T)));
        }
    }
}

이 접근 방식은 문자열 키가 있는 사전을 사용하여(대소문자 구분 및 숫자 문자열 표현에 주의) 열거형 구문 분석에도 쉽게 확장됩니다.

값이 열거형의 유효한 값인지 확인하려면 정적 메서드만 호출하면 됩니다. Enum.IsDefined.

int value = 99;//Your int value
if (Enum.IsDefined(typeof(your_enum_type), value))
{
   //Todo when value is valid
}else{
   //Todo when value is not valid
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top