Runtime: C# 6.0 - Bug in Enum.Parse

Created on 11 Mar 2017  Ā·  16Comments  Ā·  Source: dotnet/runtime

As per this document: https://msdn.microsoft.com/en-us/library/essfb559(v=vs.110).aspx, the Enum.Parse() function is supposed to throw either an ArgumentException or an OverflowException if I try to parse in a value that is not a part of the Enum. The example in that same document however uses the Enum.IsDefined() function to make the determination instead of catching that exception. The reason is simple – the Enum.Parse() no longer throws those exceptions!
Ā 
Simple script I ran in the C# Interactive in the VS 2017 IDE:
Ā 

enum fooEnum {
.Ā Ā Ā Ā  one = 1,
.Ā Ā Ā Ā  two = 2,
.Ā Ā Ā Ā  three = 3
. }
(fooEnum)Enum.Parse(typeof(fooEnum), "4")
4
fooEnum test = (fooEnum)Enum.Parse(typeof(fooEnum), "4");
test
4
fooEnum test = (fooEnum)Enum.Parse(typeof(fooEnum), "4");
Enum.GetName(typeof(fooEnum), test)
null
Ā 
Ā 
As you can see, I got no exceptions at all trying to parse in a value of ā€œ4ā€ for my fooEnum enum. I recall in earlier versions the exception used to be thrown. This broke an application for me because I was betting on the exception to be thrown which it no longer does.

All 16 comments

the Enum.Parse() function is supposed to throw either an ArgumentException or an OverflowException if I try to parse in a value that is not a part of the Enum.

To be precise it is supposed to throw ArgumentException if

value is a name, but not one of the named constants defined for the enumeration

That's not the same thing as saying "a value that is not a part of the Enum". There's no such thing as a value not being part of an enum, all values of the underlying type are part of the enum but some of them have associated names.

The reason is simple – the Enum.Parse() no longer throws those exceptions!

Yes, it does throw:
```C#
enum FooEnum
{
one = 1,
two = 2,
three = 3
}
static void Main() => Console.WriteLine((FooEnum)Enum.Parse(typeof(FooEnum), "four"));


Unhandled Exception: System.ArgumentException: Requested value 'four' was not found.
at System.Enum.EnumResult.SetFailure(ParseFailureKind failure, String failureMessageID, Object failureMessageFormatArgument)
at System.Enum.TryParseEnum(Type enumType, String value, Boolean ignoreCase, EnumResult& parseResult)
at System.Enum.Parse(Type enumType, String value, Boolean ignoreCase)
at System.Enum.Parse(Type enumType, String value)
at ConsoleApp1.Program.Main() in C:UsersMikeAppDataLocalTemporary ProjectsConsoleApp1Program.cs:line 18
```

I recall in earlier versions the exception used to be thrown.

I don't have an old version of .NET Framework handy to check but I don't think it ever behaved like that.

The specific case I was talking about is parsing in numeric values into a declared Enum variable. For example the value of an enum got persisted to a DB and is being rehydrated at a later stage. The stored value may sometimes not match (i.e., someone went into the DB and ran an adhoc UPDATE).

Then this statement does not make much sense, does it? How can a value be illegal if it is in the range of the underlying (Int32) type unless it is not defined as a member of the declared enum?

Consequently, if you do not define a constant whose value is zero, the enumeration will contain an illegal value when it is created.

From heading "Enumeration best practices" in https://msdn.microsoft.com/en-us/library/system.enum(v=vs.110).aspx.

How can a value be illegal if it is in the range of the underlying (Int32) type unless it is not defined as a member of the declared enum?

Well, as a best practice you probably don't want to use values that aren't defined. But as far as the various specifications are concerned (C# language spec, CLI spec) there's no such thing as an illegal enum value. For example the C# spec says the following:

14.5 Enum values and operations
Each enum type defines a distinct type; an explicit enumeration conversion (Ā§ā€Ž6.2.2) is required to convert between an enum type and an integral type, or between two enum types. The set of values that an enum type can take on is not limited by its enum members. 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.

Moreover 0 is a special value as it can be implicitly converted to any enum type. The suggestion to create a None member in cases where no other member has value 0 makes sense from a "best practice" point of view but it's otherwise pointless:
```C#
enum foo
{
one = 1,
two = 2
}

static void Main()
{
foo f = 0;

if (f == 0)
{
    Console.WriteLine("none");
}

}
```

So then it is a flaw in the specification. The reason that any developer uses an Enum in practice is to use specific values and with meaningful names. I have never seen or heard of anyone use values that are not defined in the named set (eg: in our above code examples, assigning a value of say 5 to the foo enum would be an error condition in the actual program that would use it --- In our contrived examples, it may be okay to have a different value).

The only exception is of course when the enum is a Flags enum and allows combination of values to create undefined values. But even then, the program doing that would expect the value to lie within the known range and not have some random value outside that range (that would be an error).

In either case, other than just the min-max range, even getting in a value that is not named would be an "error" and result in an exception in the program's business logic.

So basically the specification that you quoted above for Enum is just there to help students of the language write contrived examples and assignments. In real world programs, that specification will either result in business logic exceptions or data corruption.

I have never seen or heard of anyone use values that are not defined in the named set

It's rare but it can happen, for example when exposing native code functionality. The native side may have a well defined set of values it accepts in certain places and the .NET side ends up using an enum to represent that set of values. As the native side evolves more values are added but it's not uncommon for the .NET side to remain behind and its enum to miss the newly added values. If you want to access the new native side functionality you can cast one of the values to the enum type and use it. At the end of the day it's the native side which decides if the value is illegal or not. AFAIR something like this happens with socket options.

in our above code examples, assigning a value of say 5 to the foo enum would be an error condition in the actual program that would use it

Yes, attempting to assign 5 instead of 0 to f in my example will result in a compiler error. But that's not because there's no enum member with value 5, it's because 5 has type int and f has type foo. They're different types and an explicit conversion is required.

the program doing that would expect the value to lie within the known range and not have some random value outside that range (that would be an error)

A program expecting that would be incorrect. If a program accepts enum values from external sources (e.g. user input, files, network etc.) then that program should validate those values just like it validates any other values - strings, numbers etc.

As an aside, it's not uncommon for methods that have enum type parameters to validate them and throw some type of argument exception.

A program expecting that would be incorrect. If a program accepts enum values from external sources (e.g. user input, files, network etc.) then that program should validate those values just like it validates any other values - strings, numbers etc.

Exactly. For other data types, one would do a T.Parse() if they know it to be valid or T.TryParse() if they doubt that it would convert. These aren't possible in the case of Enum, since the values the program would consider "invalid" will be considered valid in case of the Enum. Instead, you say use the IsDefined in the case of the enum. That is a deviation from a "standard" pattern to use Parse/TryParse.

@sujaysarma

So then it is a flaw in the specification.

You could argue that another design would be better, but the fact is that this is how enums were specified since C# 1.0. And that behavior is not going to change, because it would break backwards compatibility.

It might be possible to create a new kind of "safe" enums, but I have no idea if that would be worth it.

That is a deviation from a "standard" pattern to use Parse/TryParse.

Strictly speaking no because those values aren't invalid. It's just that (for reasons that are unclear to me) many people assume that they are invalid. It's a disagreement between how people expect the feature to work and that the feature actually works.

Perhaps you can turn this into a feature request:

C# bool TryParse<T>(string value, bool parseExact, bool ignoreCase, out T result);

When parseExact is true only named values are accepted.

Not really. You could implement it in .NET Core.

@mikedn

Perhaps you can turn this into a feature request:

I don't know how to do that here. Not a Git-fan. Could you ?

@sujaysarma You don't need to do anything special, just open an issue suggesting a new API to be added. Include the method(s) signature and a few words about how it's supposed to work and why is it needed. You can look for issues having the "api-ready-for-review" label for some examples but there isn't anything magic about that.

Since there's already some discussion going on in this issue you could just edit your initial post to include the suggested TryParse overload and perhaps a Parse overload as well if you think it is useful.

It's just that (for reasons that are unclear to me) many people assume that they are invalid. It's a disagreement between how people expect the feature to work and that the feature actually works.

Well, let me try to explain why we all treat it as invalid. The typical way we learn about Enums when we learn a language is that it is a set of specific named values. We never learn it as a set of names assigned to a small subset of a larger set of underlying values. For example, it is typical to give examples of Colors, Pets, etc (even that above MSDN documentation about Enums does the same). Hence, the same parallels stick in our minds.

In our minds, it becomes An enum named Colors would have only colors in it. So Red, Blue, Yellow would be valid, but a name "square" is not. What we do NOT associate with that learning is that actually the numeric values of Red, Blue and Yellow are what are valid and the reason the compiler would not accept "square" is because that name is not defined rather than a numeric value and a numeric value would still be accepted by the compiler.

Am not sure if the C++ spec (http://en.cppreference.com/w/cpp/language/enum) is saying the same thing you are or something different.

Am not sure if the C++ spec (http://en.cppreference.com/w/cpp/language/enum) is saying the same thing you are or something different.

Yes, C# enum is very similar to C++ enum (especially the scoped enum introduced in C++11). Some quotes from the C++ std text:

7.2 Enumeration declarations

  1. An enumeration is a distinct type (3.9.2) with named constants.
  2. Each enumeration defines a type that is different from all other types. Each enumeration also has an underlying type. The underlying type can be explicitly specified using an enum-base. For a scoped enumeration type, the underlying type is int if it is not explicitly specified. In both of these cases, the underlying type is said to be fixed.
  3. For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.

Enums are just 8/16/32/64 bit integers. It's difficult or even impossible to ensure that a 32 bit piece of memory accepts only certain bit patterns. The C# language could disallow any conversion from integers to enum (e.g. disallow foo f = (foo)5;) but then there are still ways to set f to 5 - reflection, explicit struct layout, unsafe code, interop. Probably the only way to ensure that no undefined values are possible would be to make enums reference types and their values instances of themselves. Something that the C# compiler would translate to:

C# sealed class Color { private Color() { } public static readonly Color Red = new Color(); public static readonly Color Green = new Color(); public static readonly Color Blue = new Color(); }
But even then, there's still private reflection :smile:

Very valid points @mikedn. To be able to prevent all manner of preventing "unacceptable" values from being set would be ideal, yes. But not necessary. If someone wants to hack the memory to place a different value to break the system, they most likely will succeed. However, what the compiler can do, is to have structures/patterns in place to ensure that a valid high-level assignment (foo f = (foo)5, when 5 does not have a named value in the enum) can be done much more easily. It is nothing but range validation with the subset of the named constants in the enum. Hence the reason why I keep saying that this should be possible to do.

I am having an issue with Enum.TryParse() always returning true if the string looks like any number. This is causing major issues because there is no way to tell if the string is really a true value in the Enum or not!

You can use Enum.IsDefined to check if the parsed values has a corresponding enum member.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jamesqo picture jamesqo  Ā·  3Comments

btecu picture btecu  Ā·  3Comments

matty-hall picture matty-hall  Ā·  3Comments

GitAntoinee picture GitAntoinee  Ā·  3Comments

EgorBo picture EgorBo  Ā·  3Comments