Validate Enum Values

前端 未结 11 969
你的背包
你的背包 2020-11-30 09:43

I need to validate an integer to know if is a valid enum value.

What is the best way to do this in C#?

相关标签:
11条回答
  • 2020-11-30 09:59

    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())
       // ...
    
    0 讨论(0)
  • 2020-11-30 10:00

    IMHO the post marked as the answer is incorrect.
    Parameter and data validation is one of the things that was drilled into me decades ago.

    WHY

    Validation is required because essentially any integer value can be assigned to an enum without throwing an error.
    I spent many days researching C# enum validation because it is a necessary function in many cases.

    WHERE

    The main purpose in enum validation for me is in validating data read from a file: you never know if the file has been corrupted, or was modified externally, or was hacked on purpose.
    And with enum validation of application data pasted from the clipboard: you never know if the user has edited the clipboard contents.

    That said, I spent days researching and testing many methods including profiling the performance of every method I could find or design.

    Making calls into anything in System.Enum is so slow that it was a noticeable performance penalty on functions that contained hundreds or thousands of objects that had one or more enums in their properties that had to be validated for bounds.

    Bottom line, stay away from everything in the System.Enum class when validating enum values, it is dreadfully slow.

    RESULT

    The method that I currently use for enum validation will probably draw rolling eyes from many programmers here, but it is imho the least evil for my specific application design.

    I define one or two constants that are the upper and (optionally) lower bounds of the enum, and use them in a pair of if() statements for validation.
    One downside is that you must be sure to update the constants if you change the enum.
    This method also only works if the enum is an "auto" style where each enum element is an incremental integer value such as 0,1,2,3,4,.... It won't work properly with Flags or enums that have values that are not incremental.

    Also note that this method is almost as fast as regular if "<" ">" on regular int32s (which scored 38,000 ticks on my tests).

    For example:

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

    PERFORMANCE

    For those who are interested, I profiled the following variations on an enum validation, and here are the results.

    The profiling was performed on release compile in a loop of one million times on each method with a random integer input value. Each test was ran more than 10 times and averaged. The tick results include the total time to execute which will include the random number generation etc. but those will be constant across the tests. 1 tick = 10ns.

    Note that the code here isn't the complete test code, it is only the basic enum validation method. There were also a lot of additional variations on these that were tested, and all of them with results similar to those shown here that benched 1,800,000 ticks.

    Listed slowest to fastest with rounded results, hopefully no typos.

    Bounds determined in Method = 13,600,000 ticks

    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 ticks
    Note: this code version doesn't clamp to Min/Max but returns Default if out of bounds.

    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() Min/Max Constants = 43,000 ticks = the winner by 42x and 316x faster.

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

    -eol-

    0 讨论(0)
  • 2020-11-30 10:01

    Brad Abrams specifically warns against Enum.IsDefined in his post The Danger of Oversimplification.

    The best way to get rid of this requirement (that is, the need to validate enums) is to remove ways where users can get it wrong, e.g., an input box of some sort. Use enums with drop downs, for example, to enforce only valid enums.

    0 讨论(0)
  • 2020-11-30 10:04

    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).

    0 讨论(0)
  • 2020-11-30 10:09

    As others have mentioned, Enum.IsDefined is slow, something you have to be aware of if it's in a loop.

    When doing multiple comparisons, a speedier method is to first put the values into a HashSet. Then simply use Contains to check whether the value is valid, like so:

    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
    }
    
    0 讨论(0)
  • 2020-11-30 10:09

    You can use the FluentValidation for your project. Here is a simple example for the "Enum Validation"

    Let's create a EnumValidator class with using FluentValidation;

    public class EnumValidator<TEnum> : AbstractValidator<TEnum> where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        public EnumValidator(string message)
        {
            RuleFor(a => a).Must(a => typeof(TEnum).IsEnum).IsInEnum().WithMessage(message);
        }
    
    }
    

    Now we created the our enumvalidator class; let's create the a class to call enumvalidor class;

     public class Customer 
    {
      public string Name { get; set; }
      public Address address{ get; set; }
      public AddressType type {get; set;}
    }
    public class Address 
    {
      public string Line1 { get; set; }
      public string Line2 { get; set; }
      public string Town { get; set; }
      public string County { get; set; }
      public string Postcode { get; set; }
    

    }

    public enum AddressType
    {
       HOME,
       WORK
    }
    

    Its time to call our enum validor for the address type in customer class.

    public class CustomerValidator : AbstractValidator<Customer>
    {
        public CustomerValidator()
       {
         RuleFor(x => x.type).SetValidator(new EnumValidator<AddressType>("errormessage");
      }
    }
    
    0 讨论(0)
提交回复
热议问题