Why does casting int to invalid enum value NOT throw exception?
Taken from Confusion with parsing an Enum
This was a decision on the part of the people who created .NET. An enum is backed by another value type (int
, short
, byte
, etc), and so it can actually have any value that is valid for those value types.
I personally am not a fan of the way this works, so I made a series of utility methods:
/// <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);
}
}
This way, I can say:
if(!sEnum.IsDefined()) throw new Exception(...);
... or:
EnumUtil<Stooge>.Parse(s); // throws an exception if s is not a defined value.
Edit
Beyond the explanation given above, you have to realize that the .NET version of Enum follows a more C-inspired pattern than a Java-inspired one. This makes it possible to have "Bit Flag" enums which can use binary patterns to determine whether a particular "flag" is active in an enum value. If you had to define every possible combination of flags (i.e. MondayAndTuesday
, MondayAndWednesdayAndThursday
), these would be extremely tedious. So having the capacity to use undefined enum values can be really handy. It just requires a little extra work when you want a fail-fast behavior on enum types that don't leverage these sorts of tricks.
Casting to invalid enum value, Enum.ToObject, does not throw exception, sets Enum to int
Just use Enum.IsDefined
. Basically enums are just ints and you can assign any int to an enum even if it isn't defined.
if(!Enum.IsDefined(typeof(A), a))
{
throw new InvalidCastException("Not a valid value for A: " + a);
}
Assign invalidValue to Enum variable (why is this not throwing an Exception?)
According to ISO IEC 23270 (2006):
In particular, any value of the underlying type of an enum can be cast
to the enum type, and is a distinct valid value of that enum type.
Enumeration does not "inherit" from underlying type (it's only a syntax). According to the same ISO, enumeration implicitly inherits from System.Enum
and System.ValueType
.
The type
System.Enum
is the abstract base class of all enum types (this is distinct and different from the underlying type of the enum
type)Note that
System.Enum
is not itself an enum-type. Rather, it is a
class-type from which all enum-types are derived. The typeSystem.Enum
inherits from the typeSystem.ValueType
(§11.1.1), which, in turn,
inherits from typeobject
If you want to check if enum's value is valid in "common" sense, you can use Enum.IsDefined
method.
Why don't I get InvalidCastException when casting enum to integer fails?
Because it's not an invalid cast.
The value you are casting is out of range for the enum (in this case) but it's not invalid.
As the approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong a cast from integer to enum is perfectly legal.
Source - MSDN
Casting an out-of-range number to an enum in C# does not produce an exception
In C#, unlike Java, enums are not checked. You can have any value of the underlying type. This is why it's pretty important to check your input.
if(!Enum.IsDefined(typeof(MyEnum), value))
throw new ArgumentOutOfRangeException();
int to enum invalid conversion
Enumerations are just a fancy way to work with values of the underlying datatype - Int32
by default. You get named values, you lose mathematical operations. There is no rule that only values named in the declaration are valid values and therefore you get no exception or compiler error.
This becomes really obvious when you use enumerations as flags.
[Flags]
public enum Something
{
Foo = 1,
Bar = 2,
Baz = 4
}
Now something like this
var x = Something.Foo | Something.Bar; // The value is 1 | 4 = 5
is absolutely fine and again you can have values you never named. It all boils down to your wrong assumption that the set of allowed values is in someway restricted and as pointed out by other answers referring to the specification this is just not the case.
What happens if you static_cast invalid value to enum class?
What is color set to according to the standard?
Answering with a quote from the C++11 and C++14 Standards:
[expr.static.cast]/10
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range).
Let's look up the range of the enumeration values: [dcl.enum]/7
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.
Before CWG 1766 (C++11, C++14)
Therefore, for data[0] == 100
, the resulting value is specified(*), and no Undefined Behaviour (UB) is involved. More generally, as you cast from the underlying type to the enumeration type, no value in data[0]
can lead to UB for the static_cast
.
After CWG 1766 (C++17)
See CWG defect 1766.
The [expr.static.cast]p10 paragraph has been strengthened, so you now can invoke UB if you cast a value that is outside the representable range of an enum to the enum type. This still doesn't apply to the scenario in the question, since data[0]
is of the underlying type of the enumeration (see above).
Please note that CWG 1766 is considered a defect in the Standard, hence it is accepted for compiler implementers to apply to to their C++11 and C++14 compilation modes.
(*) char
is required to be at least 8 bit wide, but isn't required to be unsigned
. The maximum value storable is required to be at least 127
per Annex E of the C99 Standard.
Compare to [expr]/4
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
Before CWG 1766, the conversion integral type -> enumeration type can produce an unspecified value. The question is: Can an unspecified value be outside the representable values for its type? I believe the answer is no -- if the answer was yes, there wouldn't be any difference in the guarantees you get for operations on signed types between "this operation produces an unspecified value" and "this operation has undefined behaviour".
Hence, prior to CWG 1766, even static_cast<Color>(10000)
would not invoke UB; but after CWG 1766, it does invoke UB.
Now, the switch
statement:
[stmt.switch]/2
The condition shall be of integral type, enumeration type, or class type. [...] Integral promotions are performed.
[conv.prom]/4
A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.
Note: The underlying type of a scoped enum w/o enum-base is int
. For unscoped enums the underlying type is implementation-defined, but shall not be larger than int
if int
can contain the values of all enumerators.
For an unscoped enumeration, this leads us to /1
A prvalue of an integer type other than
bool
,char16_t
,char32_t
, orwchar_t
whose integer conversion rank (4.13) is less than the rank ofint
can be converted to a prvalue of typeint
ifint
can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of typeunsigned int
.
In the case of an unscoped enumeration, we would be dealing with int
s here. For scoped enumerations (enum class
and enum struct
), no integral promotion applies. In any way, the integral promotion doesn't lead to UB either, as the stored value is in the range of the underlying type and in the range of int
.
[stmt.switch]/5
When the
switch
statement is executed, its condition is evaluated and compared with each case constant. If one of the case constants is equal to the value of the condition, control is passed to the statement following the matchedcase
label. If nocase
constant matches the condition, and if there is adefault
label, control passes to the statement labeled by thedefault
label.
The default
label should be hit.
Note: One could take another look at the comparison operator, but it is not explicitly used in the referred "comparison". In fact, there's no hint it would introduce UB for scoped or unscoped enums in our case.
As a bonus, does the standard make any guarantees as about this but with plain enum?
Whether or not the enum
is scoped doesn't make any difference here. However, it does make a difference whether or not the underlying type is fixed. The complete [decl.enum]/7 is:
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let
K
be1
for a two's complement representation and0
for a one's complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| −K
, |emax|) and equal to 2M − 1, whereM
is a non-negative integer. bmin is zero if emin is non-negative and −(bmax +K
) otherwise.
Let's have a look at the following enumeration:
enum ColorUnfixed /* no fixed underlying type */
{
red = 0x1,
yellow = 0x2
}
Note that we cannot define this as a scoped enum, since all scoped enums have fixed underlying types.
Fortunately, ColorUnfixed
's smallest enumerator is red = 0x1
, so max(|emin| − K
, |emax|) is equal to |emax| in any case, which is yellow = 0x2
. The smallest value greater or equal to 2
, which is equal to 2M - 1 for a positive integer M
is 3
(22 - 1). (I think the intent is to allow the range to extent in 1-bit-steps.) It follows that bmax is 3
and bmin is 0
.
Therefore, 100
would be outside the range of ColorUnfixed
, and the static_cast
would produce an unspecified value before CWG 1766 and undefined behaviour after CWG 1766.
Related Topics
Using of Inotifypropertychanged
How to Run an Exe Program from a Windows Service Using C#
How to Ignore JSONproperty(Propertyname = "Somename") When Serializing JSON
Cursor.Current VS. This.Cursor
Get Mime Type from Filename Extension
Why Does This Async Action Hang When I Try and Access the Result Property of My Task
Best Way to Resolve File Path Too Long Exception
Is the Order of Static Class Initialization in C# Deterministic
How Do the Major C# Di/Ioc Frameworks Compare
Can a Byte[] Array Be Written to a File in C#
Random Number Generator with No Duplicates
How to Read the Color of a Screen Pixel
Single Controller with Multiple Get Methods in ASP.NET Web API