How to TryParse for Enum value?

前端 未结 14 913
终归单人心
终归单人心 2020-11-29 00:22

I want to write a function which can validate a given value (passed as a string) against possible values of an enum. In the case of a match, it should return th

相关标签:
14条回答
  • 2020-11-29 00:27

    Here is a custom implementation of EnumTryParse. Unlike other common implementations, it also supports enum marked with the Flags attribute.

        /// <summary>
        /// Converts the string representation of an enum to its Enum equivalent value. A return value indicates whether the operation succeeded.
        /// This method does not rely on Enum.Parse and therefore will never raise any first or second chance exception.
        /// </summary>
        /// <param name="type">The enum target type. May not be null.</param>
        /// <param name="input">The input text. May be null.</param>
        /// <param name="value">When this method returns, contains Enum equivalent value to the enum contained in input, if the conversion succeeded.</param>
        /// <returns>
        /// true if s was converted successfully; otherwise, false.
        /// </returns>
        public static bool EnumTryParse(Type type, string input, out object value)
        {
            if (type == null)
                throw new ArgumentNullException("type");
    
            if (!type.IsEnum)
                throw new ArgumentException(null, "type");
    
            if (input == null)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
    
            input = input.Trim();
            if (input.Length == 0)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
    
            string[] names = Enum.GetNames(type);
            if (names.Length == 0)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
    
            Type underlyingType = Enum.GetUnderlyingType(type);
            Array values = Enum.GetValues(type);
            // some enums like System.CodeDom.MemberAttributes *are* flags but are not declared with Flags...
            if ((!type.IsDefined(typeof(FlagsAttribute), true)) && (input.IndexOfAny(_enumSeperators) < 0))
                return EnumToObject(type, underlyingType, names, values, input, out value);
    
            // multi value enum
            string[] tokens = input.Split(_enumSeperators, StringSplitOptions.RemoveEmptyEntries);
            if (tokens.Length == 0)
            {
                value = Activator.CreateInstance(type);
                return false;
            }
    
            ulong ul = 0;
            foreach (string tok in tokens)
            {
                string token = tok.Trim(); // NOTE: we don't consider empty tokens as errors
                if (token.Length == 0)
                    continue;
    
                object tokenValue;
                if (!EnumToObject(type, underlyingType, names, values, token, out tokenValue))
                {
                    value = Activator.CreateInstance(type);
                    return false;
                }
    
                ulong tokenUl;
                switch (Convert.GetTypeCode(tokenValue))
                {
                    case TypeCode.Int16:
                    case TypeCode.Int32:
                    case TypeCode.Int64:
                    case TypeCode.SByte:
                        tokenUl = (ulong)Convert.ToInt64(tokenValue, CultureInfo.InvariantCulture);
                        break;
    
                    //case TypeCode.Byte:
                    //case TypeCode.UInt16:
                    //case TypeCode.UInt32:
                    //case TypeCode.UInt64:
                    default:
                        tokenUl = Convert.ToUInt64(tokenValue, CultureInfo.InvariantCulture);
                        break;
                }
    
                ul |= tokenUl;
            }
            value = Enum.ToObject(type, ul);
            return true;
        }
    
        private static char[] _enumSeperators = new char[] { ',', ';', '+', '|', ' ' };
    
        private static object EnumToObject(Type underlyingType, string input)
        {
            if (underlyingType == typeof(int))
            {
                int s;
                if (int.TryParse(input, out s))
                    return s;
            }
    
            if (underlyingType == typeof(uint))
            {
                uint s;
                if (uint.TryParse(input, out s))
                    return s;
            }
    
            if (underlyingType == typeof(ulong))
            {
                ulong s;
                if (ulong.TryParse(input, out s))
                    return s;
            }
    
            if (underlyingType == typeof(long))
            {
                long s;
                if (long.TryParse(input, out s))
                    return s;
            }
    
            if (underlyingType == typeof(short))
            {
                short s;
                if (short.TryParse(input, out s))
                    return s;
            }
    
            if (underlyingType == typeof(ushort))
            {
                ushort s;
                if (ushort.TryParse(input, out s))
                    return s;
            }
    
            if (underlyingType == typeof(byte))
            {
                byte s;
                if (byte.TryParse(input, out s))
                    return s;
            }
    
            if (underlyingType == typeof(sbyte))
            {
                sbyte s;
                if (sbyte.TryParse(input, out s))
                    return s;
            }
    
            return null;
        }
    
        private static bool EnumToObject(Type type, Type underlyingType, string[] names, Array values, string input, out object value)
        {
            for (int i = 0; i < names.Length; i++)
            {
                if (string.Compare(names[i], input, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = values.GetValue(i);
                    return true;
                }
            }
    
            if ((char.IsDigit(input[0]) || (input[0] == '-')) || (input[0] == '+'))
            {
                object obj = EnumToObject(underlyingType, input);
                if (obj == null)
                {
                    value = Activator.CreateInstance(type);
                    return false;
                }
                value = obj;
                return true;
            }
    
            value = Activator.CreateInstance(type);
            return false;
        }
    
    0 讨论(0)
  • 2020-11-29 00:27

    In the end you have to implement this around Enum.GetNames:

    public bool TryParseEnum<T>(string str, bool caseSensitive, out T value) where T : struct {
        // Can't make this a type constraint...
        if (!typeof(T).IsEnum) {
            throw new ArgumentException("Type parameter must be an enum");
        }
        var names = Enum.GetNames(typeof(T));
        value = (Enum.GetValues(typeof(T)) as T[])[0];  // For want of a better default
        foreach (var name in names) {
            if (String.Equals(name, str, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) {
                value = (T)Enum.Parse(typeof(T), name);
                return true;
            }
        }
        return false;
    }
    

    Additional notes:

    • Enum.TryParse is included in .NET 4. See here http://msdn.microsoft.com/library/dd991876(VS.100).aspx
    • Another approach would be to directly wrap Enum.Parse catching the exception thrown when it fails. This could be faster when a match is found, but will likely to slower if not. Depending on the data you are processing this may or may not be a net improvement.

    EDIT: Just seen a better implementation on this, which caches the necessary information: http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5

    0 讨论(0)
  • 2020-11-29 00:28

    Have a look at the Enum class (struct ? ) itself. There is a Parse method on that but I'm not sure about a tryparse.

    0 讨论(0)
  • 2020-11-29 00:30

    I have an optimised implementation you could use in UnconstrainedMelody. Effectively it's just caching the list of names, but it's doing so in a nice, strongly typed, generically constrained way :)

    0 讨论(0)
  • 2020-11-29 00:30

    Is caching a dynamically generated function/dictionary permissable?

    Because you don't (appear to) know the type of the enum ahead of time, the first execution could generate something subsequent executions could take advantage of.

    You could even cache the result of Enum.GetNames()

    Are you trying to optimize for CPU or Memory? Do you really need to?

    0 讨论(0)
  • 2020-11-29 00:33

    The only way to avoid exception handling is to use the GetNames() method, and we all know that exceptions shouldn't be abused for common application logic :)

    0 讨论(0)
提交回复
热议问题