Implementation of Enum.TryParse in .NET 3.5

后端 未结 4 335
执念已碎
执念已碎 2021-01-12 14:34

How could I implement the .NET 4\'s Enum.TryParse method in .NET 3.5?

public static bool TryParse(string          


        
相关标签:
4条回答
  • 2021-01-12 15:01

    Took longer than I hoped to get this right, but it works and has been tested. Hope this saves someone some time!

        private static readonly char[] FlagDelimiter = new [] { ',' };
    
        public static bool TryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct {
            if (string.IsNullOrEmpty(value)) {
                result = default(TEnum);
                return false;
            }
    
            var enumType = typeof(TEnum);
    
            if (!enumType.IsEnum)
                throw new ArgumentException(string.Format("Type '{0}' is not an enum", enumType.FullName));
    
    
            result = default(TEnum);
    
            // Try to parse the value directly 
            if (Enum.IsDefined(enumType, value)) {
                result = (TEnum)Enum.Parse(enumType, value);
                return true;
            }
    
            // Get some info on enum
            var enumValues = Enum.GetValues(enumType);
            if (enumValues.Length == 0)
                return false;  // probably can't happen as you cant define empty enum?
            var enumTypeCode = Type.GetTypeCode(enumValues.GetValue(0).GetType());
    
            // Try to parse it as a flag 
            if (value.IndexOf(',') != -1) {
                if (!Attribute.IsDefined(enumType, typeof(FlagsAttribute)))
                    return false;  // value has flags but enum is not flags
    
                // todo: cache this for efficiency
                var enumInfo = new Dictionary<string, object>();
                var enumNames = Enum.GetNames(enumType);
                for (var i = 0; i < enumNames.Length; i++)
                    enumInfo.Add(enumNames[i], enumValues.GetValue(i));
    
                ulong retVal = 0;
                foreach(var name in value.Split(FlagDelimiter)) {
                    var trimmedName = name.Trim();
                    if (!enumInfo.ContainsKey(trimmedName))
                        return false;   // Enum has no such flag
    
                    var enumValueObject = enumInfo[trimmedName];
                    ulong enumValueLong;
                    switch (enumTypeCode) {
                        case TypeCode.Byte:
                            enumValueLong = (byte)enumValueObject;
                            break;
                        case TypeCode.SByte:
                            enumValueLong = (byte)((sbyte)enumValueObject);
                            break;
                        case TypeCode.Int16:
                            enumValueLong = (ushort)((short)enumValueObject);
                            break;
                        case TypeCode.Int32:
                            enumValueLong = (uint)((int)enumValueObject);
                            break;
                        case TypeCode.Int64:
                            enumValueLong = (ulong)((long)enumValueObject);
                            break;
                        case TypeCode.UInt16:
                            enumValueLong = (ushort)enumValueObject;
                            break;
                        case TypeCode.UInt32:
                            enumValueLong = (uint)enumValueObject;
                            break;
                        case TypeCode.UInt64:
                            enumValueLong = (ulong)enumValueObject;
                            break;
                        default:
                            return false;   // should never happen
                    }
                    retVal |= enumValueLong;
                }
                result = (TEnum)Enum.ToObject(enumType, retVal);
                return true;
            }
    
            // the value may be a number, so parse it directly
            switch (enumTypeCode) {
                case TypeCode.SByte:
                    sbyte sb;
                    if (!SByte.TryParse(value, out sb))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, sb);
                    break;
                case TypeCode.Byte:
                    byte b;
                    if (!Byte.TryParse(value, out b))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, b);
                    break;
                case TypeCode.Int16:
                    short i16;
                    if (!Int16.TryParse(value, out i16))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, i16);
                    break;
                case TypeCode.UInt16:
                    ushort u16;
                    if (!UInt16.TryParse(value, out u16))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, u16);
                    break;
                case TypeCode.Int32:
                    int i32;
                    if (!Int32.TryParse(value, out i32))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, i32);
                    break;
                case TypeCode.UInt32:
                    uint u32;
                    if (!UInt32.TryParse(value, out u32))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, u32);
                    break;
                case TypeCode.Int64:
                    long i64;
                    if (!Int64.TryParse(value, out i64))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, i64);
                    break;
                case TypeCode.UInt64:
                    ulong u64;
                    if (!UInt64.TryParse(value, out u64))
                        return false;
                    result = (TEnum)Enum.ToObject(enumType, u64);
                    break;
                default:
                    return false; // should never happen
            }
    
            return true;
        }
    
    0 讨论(0)
  • 2021-01-12 15:02

    It won't be a static method on Enum (static extension methods don't quite make sense), but it should work

    public static class EnumHelpers
    {
        public static bool TryParse<TEnum>(string value, out TEnum result)
            where TEnum : struct
        {
            try
            {
                result = (TEnum)Enum.Parse(typeof(TEnum), value); 
            }
            catch
            {
                result = default;
                return false;
            }
    
            return true;
        }
    }
    
    0 讨论(0)
  • 2021-01-12 15:02

    At NLog we also needed Enum.TryParse for .Net 3.5. We have implemented the basic features (just parse, case sensitive and insensitive, no flags) influenced by this post.

    This basic implementation is highly unit tested so it has the same behavior as Microsoft`s .Net 4 implementation.

    /// <summary>
    /// Enum.TryParse implementation for .net 3.5 
    /// 
    /// </summary>
    /// <returns></returns>
    /// <remarks>Don't uses reflection</remarks>
    // ReSharper disable once UnusedMember.Local
    private static bool TryParseEnum_net3<TEnum>(string value, bool ignoreCase, out TEnum result) where TEnum : struct
    {
        var enumType = typeof(TEnum);
        if (!enumType.IsEnum())
            throw new ArgumentException($"Type '{enumType.FullName}' is not an enum");
    
        if (StringHelpers.IsNullOrWhiteSpace(value))
        {
            result = default(TEnum);
            return false;
        }
    
        try
        {
            result = (TEnum)Enum.Parse(enumType, value, ignoreCase);
            return true;
        }
        catch (Exception)
        {
            result = default(TEnum);
            return false;
        }
    }
    
    

    And is using:

    public static class StringHelpers
    {
        /// <summary>
        /// IsNullOrWhiteSpace, including for .NET 3.5
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        [ContractAnnotation("value:null => true")]
        internal static bool IsNullOrWhiteSpace(string value)
        {
    #if NET3_5
    
            if (value == null) return true;
            if (value.Length == 0) return true;
            return String.IsNullOrEmpty(value.Trim());
    #else
            return string.IsNullOrWhiteSpace(value);
    #endif
        }
    }
    

    The code can be found at the NLog GitHub, and also the unit tests are on GitHub (xUnit)

    0 讨论(0)
  • 2021-01-12 15:14

    I dislike using a try-catch to handle any conversion failures or other non-exceptional events as part of the normal flow of my application, so my own Enum.TryParse method for .NET 3.5 and earlier makes use of the Enum.IsDefined() method to make sure the there will not be an exception thrown by Enum.Parse(). You can also include some null checks on value to prevent an ArgumentNullException if value is null.

    public static bool TryParse<TEnum>(string value, out TEnum result)
        where TEnum : struct, IConvertible
    {
        var retValue = value == null ? 
                    false : 
                    Enum.IsDefined(typeof(TEnum), value);
        result = retValue ?
                    (TEnum)Enum.Parse(typeof(TEnum), value) :
                    default(TEnum);
        return retValue;
    }
    

    Obviously this method will not reside in the Enum class so you will need a class to include this in that would be appropriate.

    One limitation is the lack of an enum constraint on generic methods, so you would have to consider how you want to handle incorrect types. Enum.IsDefined will throw an ArgumentException if TEnum is not an enum but the only other option is a runtime check and throwing a different exception, so I generally do not add an additional check and just let the type checking in these methods handle for me. I'd consider adding IConvertible as another constraint, just to help constrain the type even more.

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