为什么将int强制转换为无效枚举值NOT会引发异常
本文关键字:NOT 异常 枚举 无效 int 转换 为什么 | 更新日期: 2023-09-27 17:59:47
如果我有这样的枚举:
enum Beer
{
Bud = 10,
Stella = 20,
Unknown
}
为什么它在将这些值之外的int
强制转换为Beer
类型时不抛出异常?
例如,以下代码没有抛出异常,它向控制台输出"50":
int i = 50;
var b = (Beer) i;
Console.WriteLine(b.ToString());
我觉得这很奇怪。。。有人能澄清吗?
取自解析枚举的困惑
这是创建.NET的人的决定。枚举由另一种值类型(int
、short
、byte
等)支持,因此它实际上可以具有对这些值类型有效的任何值。
我个人并不喜欢这种工作方式,所以我制作了一系列实用方法:
/// <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.
编辑
除了上面给出的解释之外,您还必须意识到,与Java模式相比,.NET版本的Enum遵循了一种更受C启发的模式。这使得"位标志"枚举成为可能,它可以使用二进制模式来确定特定的"标志"是否在枚举值中处于活动状态。如果必须定义所有可能的标志组合(即MondayAndTuesday
、MondayAndWednesdayAndThursday
),那么这些操作将非常繁琐。因此,能够使用未定义的枚举值非常方便。当您想要对不利用这些技巧的枚举类型执行快速故障行为时,只需要做一些额外的工作。
枚举经常用作标志:
[Flags]
enum Permission
{
None = 0x00,
Read = 0x01,
Write = 0x02,
}
...
Permission p = Permission.Read | Permission.Write;
p的值是整数3,它不是枚举的值,但显然是一个有效值。
我个人宁愿看到一个不同的解决方案;我宁愿有能力将"位数组"整数类型和"一组不同的值"类型作为两种不同的语言功能,而不是将它们合并为"enum"。但这正是最初的语言和框架设计者所想到的;因此,我们必须允许枚举的未声明值成为合法值。
简短的回答是:语言设计者决定以这种方式设计语言。
答案很长:C#语言规范的Section 6.2.2: Explicit enumeration conversions
说:
通过将任何参与的枚举类型视为该枚举类型的基础类型,然后在生成的类型之间执行隐式或显式数字转换,来处理两个类型之间的显式枚举转换。例如,给定一个枚举类型为E且底层类型为int的枚举,从E到字节的转换被处理为从int到byte的显式数字转换(§6.2.1),从byte到E的转换则被处理为字节到int的隐式数值转换(§6.1.2)。
基本上,在执行转换操作时,枚举被视为底层类型。默认情况下,枚举的基础类型是Int32
,这意味着转换的处理方式与转换为Int32
完全相同。这意味着任何有效的int
值都是允许的。
我怀疑这主要是出于性能原因。通过使enum
成为一个简单的积分类型并允许任何积分类型转换,CLR不需要进行所有额外的检查。这意味着,与使用整数相比,使用enum
实际上没有任何性能损失,这反过来又有助于鼓励使用它。
来自文档:
Days类型的变量可以是指定的范围内的任何值底层类型;值不是仅限于命名的常量。