Development Tip

열거 형 값 검증

yourdevel 2020. 11. 17. 21:10
반응형

열거 형 값 검증


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

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 Convert Int32 with casts = 1,800,000 ticks

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는 자신의 게시물 The Danger of Oversimplification 에서 특별히 경고합니다 .

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


이 답변은 System.Enum의 성능 문제를 제기하는 deegee의 답변에 대한 응답이므로 엄격한 성능 시나리오에서 열거 형 유효성 검사를 더 다루면서 선호하는 일반적인 답변으로 사용해서는 안됩니다.

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

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);
}

I'm sure the EnumValidator classes could written more efficiently (it’s just a quick hack to demonstrate) but quite frankly who cares what happens outside the import loop? The only bit that needs to be super-fast is within the loop. This was the reason for taking the abstract class route, to avoid an unnecessary if-enumContiguous-then-else in the loop (the factory Create essentially does this upfront). You will note a bit of hypocrisy, for brevity this code constrains functionality to int-enums. I should be making use of IConvertible rather than using int's directly but this answer is already wordy enough!


This is how I do it based on multiple posts online. The reason for doing this is to make sure enums marked with Flags attribute can also be successfully validated.

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);
    }
}

Here is a fast generic solution, using a statically-constucted HashSet<T>.

You can define this once in your toolbox, and then use it for all your enum validation.

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)));
        }
    }
}

Note that this approach is easily extended to enum parsing as well, by using a dictionary with string keys (minding case-insensitivity and numeric string representations).


Building upon Timo's answer, I have crafted the following extension method (C# 6 syntax) to provide a fast, generic solution.

This avoids the performance problems of Enum.IsDefined, and with a much cleaner syntax as a bonus.

public static class EnumHelpers
{
    /// <summary>
    /// Returns whether the given enum value is a defined value for its type.
    /// </summary>
    public static bool IsDefined<T>(this T enumValue)
        where T : Enum
        => EnumValueCache<T>.DefinedValues.Contains(enumValue);

    /// <summary>
    /// Caches the defined values for each enum type for which this class is accessed.
    /// </summary>
    private static class EnumValueCache<T>
        where T : Enum
    {
        public static readonly HashSet<T> DefinedValues = new HashSet<T>((T[])Enum.GetValues(typeof(T)));
    }
}

Usage:

if (!myEnumValue.IsDefined())
   // ...

I found this link that answers it quite well. It uses:

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

To validate if a value is a valid value in an enumeration, you only need to call the static method 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
}

참고URL : https://stackoverflow.com/questions/13615/validate-enum-values

반응형