Possible for Enum Integer Cast Exception?
Given the code below, is there any possiblity the int
cast could throw an exception?
static void foo(Type typeEnum)
{
if (typeEnum.IsEnum)
{
foreach (var enumVal in Enum.GetValues(typeEnum))
{
var _val = (int)enumVal;
开发者_运维百科}
}
}
Yes, if the enum
backing type is not int
, like:
public enum X : long
{
A,
B,
C
}
This will throw. This is because the enum
values are boxes as object
, and you can't cast 'object' to 'int' unless the contained value is actually an 'int'.
You could alleviate this by doing a Convert.ToInt32()
which will work for all backing types of int
or smaller:
static void foo(Type typeEnum)
{
if (typeEnum.IsEnum)
{
foreach (var enumVal in Enum.GetValues(typeEnum))
{
var _val = Convert.ToInt32(enumVal);
}
}
}
Or, if you want to assume int
and just be safer, you can check the underlying type of the enum
like:
if (Enum.GetUnderlyingType(typeEnum) != typeof(int))
{
throw new ArgumentException("This method only accepts int enums.");
}
Alternatively, you could assume a type of long
if signed or ulong
if unsigned (you can have negative enum
values, but tend to be rarer):
static void foo(Type typeEnum)
{
if (typeEnum.IsEnum)
{
foreach (var enumVal in Enum.GetValues(typeEnum))
{
var _val = Convert.ToInt64(enumVal);
}
}
}
This is why it's probably safer to make some assumptions and check them on the call. Anything you do to unbox the value has the potential of throwing or overflowing.
You could even go generic and have the user pass in the type they want to get out:
static IEnumerable<ToType> foo<ToType>(Type typeEnum)
{
if (typeEnum.IsEnum)
{
foreach (var enumVal in Enum.GetValues(typeEnum))
{
yield return (ToType)Convert.ChangeType(enumVal, typeof(ToType));
}
}
}
So you could invoke this:
IEnumerable<int> values foo<int>(typeof(YourEnum));
Then if they get an exception, it falls on them to specify the right size type...
Enums are strange beasts. They may inherit from long and still be enums.
I'm pretty sure this theoretical code would throw a cast exception should you take an enum that does this.
As James Michael Hare mentioned, enums can be long
s. However, using Convert.ToInt32
is not good enough because you could still get an overflow exception. Imagine the following enum that has a value that is bigger than can even fit in an int:
public enum BigEnum : long
{
BigValue = (long)int.MaxValue + 5
}
In that case, there is no way to convert that value to an int
because it is too big. However, you can use logic like this and it will not throw:
static void foo(Type typeEnum)
{
var underlyingType = Enum.GetUnderlyingType(typeEnum);
if (typeEnum.IsEnum)
{
foreach (var enumVal in Enum.GetValues(typeEnum))
{
var _val = Convert.ChangeType(enumVal, underlyingType);
}
}
}
It uses the Enum.GetUnderlyingType
method which ensures that the call to ChangeType
will be working with the correct type. (in fact, that MSDN page for GetUnderlyingType has sample code that does almost this exact thing my sample code does).
Enum.GetValues
actually returns an array of that particular enum type.
What's not mentioned is that the particular array is also castable to int[]
if TEnum:int
or TEnum:uint
(CLR arrays for primitives are convertible regardless of signedness)
Otherwise to be defensive you can always use the Convert.ChangeType(object,Type)
api to be safe.
As a result you can write your code to be like so:
static void foo(Type typeEnum)
{
if (typeEnum.IsEnum)
{
var array = Enum.GetValues(typeEnum)
int[] arrayAsInts = array as int[];
if(arrayAsInts != null)
{
foreach (var enumVal in arrayAsInts)
{
var _val = enumVal;
}
}
else
{
foreach (var enumVal in array)
{
var _val = Convert.ChangeType(enumVal,typeof(int));
}
}
}
}
精彩评论