【问题标题】:Confusion with parsing an Enum解析枚举的困惑
【发布时间】:2011-02-03 22:36:40
【问题描述】:

我正在将字符串类型的数值转换为相应的枚举。在测试我的代码时,我发现了让我感到困惑的有趣行为。

使用下面的代码示例,有人可以阐明如果/当“s”变量的值与枚举值之一不匹配时,为什么不引发异常?另外,如何将 sEnum var 设置为 Stooge 枚举定义中不存在的值?

class Program
{
    enum Stooge
    {
        Unspecified,
        Moe,
        Larry,
        Curly,
        Shemp
    }

    static void Main(string[] args)
    {
        while (true)
        {
            Console.WriteLine("Enter a number...");

            string s = Console.ReadLine();
            Stooge sEnum = (Stooge)(int.Parse(s)); //Why doesn't this line throw if s != 0, 1, 2, 3, or 4?

            Console.WriteLine("\r\nYou entered: {0}\r\nEnum String Value: {1}\r\nEnum Int Value: {2}\r\n", s, sEnum.ToString(), (int)sEnum);
        }
    }
}

【问题讨论】:

    标签: c# parsing enums


    【解决方案1】:

    这是创建 .NET 的人的决定。枚举由另一个值类型(intshortbyte 等)支持,因此它实际上可以具有对这些值类型有效的任何值。

    我个人不是很喜欢这种工作方式,所以我做了一系列实用方法:

    /// <summary>
    /// Utility methods for enum values. This static type will fail to initialize 
    /// (throwing a <see cref="TypeInitializationException"/>) if
    /// you try to provide a value that is not an enum.
    /// </summary>
    /// <typeparam name="T">An enum type. </typeparam>
    public static class EnumUtil<T>
        where T : struct, IConvertible // Try to get as much of a static check as we can.
    {
        // The .NET framework doesn't provide a compile-checked
        // way to ensure that a type is an enum, so we have to check when the type
        // is statically invoked.
        static EnumUtil()
        {
            // Throw Exception on static initialization if the given type isn't an enum.
            Require.That(typeof (T).IsEnum, () => typeof(T).FullName + " is not an enum type.");
        }
    
        /// <summary>
        /// In the .NET Framework, objects can be cast to enum values which are not
        /// defined for their type. This method provides a simple fail-fast check
        /// that the enum value is defined, and creates a cast at the same time.
        /// Cast the given value as the given enum type.
        /// Throw an exception if the value is not defined for the given enum type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumValue"></param>
        /// <exception cref="InvalidCastException">
        /// If the given value is not a defined value of the enum type.
        /// </exception>
        /// <returns></returns>
        public static T DefinedCast(object enumValue)
    
        {
            if (!System.Enum.IsDefined(typeof(T), enumValue))
                throw new InvalidCastException(enumValue + " is not a defined value for enum type " +
                                               typeof (T).FullName);
            return (T) enumValue;
        }
    
        /// <summary>
        /// 
        /// </summary>
        /// <param name="enumValue"></param>
        /// <returns></returns>
        public static T Parse(string enumValue)
        {
            var parsedValue = (T)System.Enum.Parse(typeof (T), enumValue);
            //Require that the parsed value is defined
            Require.That(parsedValue.IsDefined(), 
                () => new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", 
                    enumValue, typeof(T).FullName)));
            return parsedValue;
        }
    
        public static bool IsDefined(T enumValue)
        {
            return System.Enum.IsDefined(typeof (T), enumValue);
        }
    
    }
    
    public static class EnumExtensions
    {
        public static bool IsDefined<T>(this T enumValue)
            where T : struct, IConvertible
        {
            return EnumUtil<T>.IsDefined(enumValue);
        }
    }
    

    这样,我可以说:

    if(!sEnum.IsDefined()) throw new Exception(...);
    

    ... 或:

    EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.
    

    更新

    正如 Brandon Kramer 在 cmets 中指出的那样,C# 7.3 引入了一些新的泛型类型,允许将上面的 where T : struct, IConvertible 替换为 where T : Enum,以便更好地在编译时检查该类型的 Enum-ness传入。这样就可以去掉 EnumUtil 的静态构造函数中的 guard 语句。

    【讨论】:

    • Require.That 也来自我自己的图书馆,顺便说一句。您可以将其替换为if(!...) throw new Exception(...);
    • 我喜欢 Require。那你能分享一下你是怎么做到的吗?
    • parsedValue.IsDefined() 对我不起作用 - IsDefined() 无法解决(?) - 如果相关,我正在使用 if(!...
    • @SimonHeffer:在调试扩展方法时,我发现直接调用它们很有用:EnumUtil&lt;MyEnumType&gt;.IsDefined(parsedValue)。一旦你这样做了,IDE 往往会给你更多有用的错误消息。您可能缺少 using 语句或引用,或者 parsedValue 的类型实际上不是枚举。
    • @StriplingWarrior - 我想我可能在 C#5 上。也许尚不支持使用 IsDefined 作为枚举对象上的方法? (与将对象作为参数传递的语法相反。这有效:public static T Parse(string enumValue) { var parsedValue = (T)System.Enum.Parse(typeof(T), enumValue); if (!IsDefined(parsedValue)) throw new ArgumentException(string.Format("{0} is not a defined value for enum type {1}", enumValue, typeof(T).FullName)); return parsedValue; }
    【解决方案2】:

    从技术上讲,枚举只是一个 int(或任何您定义的枚举的基础类型)。您可以通过调用Enum.IsDefined 来检查枚举中的相应值。更多信息在这里:Cast int to enum in C#

    【讨论】:

      【解决方案3】:

      Enum 是对int 的真正薄包装。基本上它是int + 可能值的静态集合(某种常量)。所有检查都在编译时进行,类型检查等。但是当您实际将 int 转换为 enum 时,运行时并不关心。所以请验证您的输入!

      【讨论】:

        【解决方案4】:

        我将实现从https://stackoverflow.com/a/4892571/275388 更改为解决两个问题

        1. DefinedCast(object enumValue) 签名表明该代码可以与stringint 类型一起使用(也可以不必要地将后者框起来)。
        2. Enum.IsDefined/Enum.Parse 都通过 Enum.GetValues(typeof(TEnum)) 分配一个数组,这实际上导致了我的用例的针慢 - 这可以通过缓存地图来避免。

        因此我结束了

        public static class EnumExtensions
        {
            public static TEnum DefinedCast<TEnum>(string value)
                where TEnum : struct, IComparable, IFormattable, IConvertible
            {
                if (!MapByString<TEnum>.Instance.TryGetValue(value, out TEnum @enum))
                {
                    throw new InvalidCastException(FormattableString.Invariant($"'{value}' is not a defined value"));
                }
        
                return @enum;
            }
        
            public static TEnum DefinedCast<TEnum>(int value)
                where TEnum : struct, IComparable, IFormattable, IConvertible
            {
                if (!MapByInteger<TEnum>.Instance.TryGetValue(value, out TEnum @enum))
                {
                    throw new InvalidCastException(FormattableString.Invariant($"'{value}' is not a defined value"));
                }
        
                return @enum;
            }
        
            private static class MapByInteger<TEnum>
                where TEnum : struct, IComparable, IFormattable, IConvertible
            {
                public static readonly Dictionary<int, TEnum> Instance = ((TEnum[])Enum.GetValues(typeof(TEnum))).ToDictionary(e => (int)Convert.ChangeType(e, typeof(int), CultureInfo.InvariantCulture));
            }
        
            private static class MapByString<TEnum>
                where TEnum : struct, IComparable, IFormattable, IConvertible
            {
                public static readonly Dictionary<string, TEnum> Instance = ((TEnum[])Enum.GetValues(typeof(TEnum))).ToDictionary(e => e.ToString(CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase);
            }
        }
        

        【讨论】:

          【解决方案5】:

          如果您不想在传递的值不可解析的情况下抛出异常,请使用 int.Parse()。如果您不想解析可能无效的值而不抛出异常,请使用 int.TryParse()。

          【讨论】:

          • 这不是他想要的。如果该值不是他的枚举类型的定义值,他希望抛出异常,无论它是否是有效的 int 值。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-31
          • 1970-01-01
          • 2011-01-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多