为什么将int强制转换为无效的枚举值不会引发异常?


122

如果我有这样的枚举:

enum Beer
{
    Bud = 10,
    Stella = 20,
    Unknown
}

int将这些值之外的an强制转换为类型时,为什么不引发异常Beer

例如,以下代码不会引发异常,它会向控制台输出“ 50”:

int i = 50;
var b = (Beer) i;

Console.WriteLine(b.ToString());

我觉得这很奇怪...有人可以澄清吗?


27
请注意,您始终可以使用Enum.IsDefined检查值是否有效。
蒂姆·施密特

很酷,我不知道,可能会在我当前要解决的问题中使用它
jcvandan 2011年


1
因Stella的身价是Bud的两倍而受到赞誉。
丹·贝查德

Answers:


80

混淆中解析枚举中获取

这是创建.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.

编辑

除了上面给出的解释之外,您还必须意识到Enum的.NET版本遵循的样式比受Java启发的样式更受C启发。这样就可以拥有“位标志”枚举,该枚举可以使用二进制模式来确定某个特定“标志”在枚举值中是否处于活动状态。如果你必须定义标志的每一个可能的组合(即MondayAndTuesdayMondayAndWednesdayAndThursday),这将是非常乏味的。因此,具有使用未定义的枚举值的能力非常方便。当您想要对不利用这些技巧的枚举类型执行快速失败操作时,只需要做一些额外的工作。


很好...我也不喜欢它的工作方式,对我来说似乎很奇怪
jcvandan 2011年

3
@dormisher:“这太疯狂了,但是有办法。” 看到我的编辑。
StriplingWarrior

2
@StriplingWarrior,请关注。Java等效项为EnumSet.of(星期一,星期三,星期四)。EnumSet内只有一个long。因此提供了一个不错的API,而没有太多的效率损失。
伊恩

2
“ Require.That()” => stackoverflow.com/questions/4892548/…–
约翰

@StriplingWarrior在解析方法中,您试图像扩展方法一样调用IsDefined。但是,由于您尝试使EnumUtil类具有约束(对静态枚举进行静态检查,我非常喜欢),因此IsDefined方法不能成为扩展方法。但是实际上它告诉我,不能在泛型类上创建扩展方法。有任何想法吗?
CJC

55

枚举通常用作标志:

[Flags]
enum Permission
{
    None = 0x00,
    Read = 0x01,
    Write = 0x02,
}
...

Permission p = Permission.Read | Permission.Write;

p的值是整数3,它不是枚举的值,但显然是有效值。

我个人更希望看到一个不同的解决方案。我宁愿能够将“位数组”整数类型 “一组不同的值”类型设为两种不同的语言功能,而不是将它们都合并为“枚举”。但这就是原始语言和框架设计者想出的。结果,我们必须允许枚举的未声明值是合法值。


19
但是3“显然是有效值”的某种程度加强了OP的观点。[Flags]属性可以告诉编译器寻找已定义的枚举或有效值。只是在说'。
LarsTech 2011年

15

简短的答案:语言设计师决定以这种方式设计语言。

Section 6.2.2: Explicit enumeration conversionsC#语言规范的长答案:

通过将任何参与的枚举类型视为该枚举类型的基础类型,然后执行结果类型之间的隐式或显式数值转换,来处理两种类型之间的显式枚举转换。例如,给定具有int和基础类型为int的枚举类型E,从E到字节的转换将作为从int到字节的显式数值转换(第6.2.1节)进行处理,而将从字节到E的转换处理为从字节到整数的隐式数值转换(第6.1.2节)。

基本上,在进行转换操作时,枚举被视为基础类型。默认情况下,枚举的基础类型为Int32,这意味着转换与转换完全一样Int32。这意味着任何有效值int都是允许的。

我怀疑这样做主要是出于性能方面的考虑。通过创建enum简单的整数类型并允许任何整数类型转换,CLR不需要执行所有额外的检查。这意味着enum与使用整数相比,使用a确实没有任何性能损失,这反过来又有助于鼓励使用它。


你觉得这个奇怪吗?我认为枚举的主要目的之一是安全地对一定范围的整数进行分组,我们也可以赋予各个含义。对我来说,允许枚举类型保留任何整数值都无法达到目的。
jcvandan 2011年

@dormisher:我刚刚编辑并在末尾添加了一些内容。提供您所追求的“安全性”涉及成本。话虽如此,在正常使用情况下,这根本不是问题。
Reed Copsey

9

文档中

可以为类型Days的变量分配基础类型范围内的任何值;值不限于命名常量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.