tags:

views:

854

answers:

11

This has always bugged me. Perhaps someone with some hardcore knowledge of .NET internals can explain it to me.

Suppose I define an enum as follows:

public enum Foo
{
   Eenie = 1,
   Meenie = 2,
   Miney = 3,
   Moe = 4
}

Now, also suppose that somewhere in my code, I have the following code:

int bar = (Foo)5;

This will compile just fine, and no exception will be raised whatsoever, even though the value 5 is clearly not a valid value defined in Foo.

Or, consider the following:

public void ProcessFoo(Foo theFoo)
{
    // Do processing
}

public static void Main()
{
    ProcessFoo((Foo)5);
}

Again, no exception.

In my mind, this should result in a type mismatch exception, because 5 is not a Foo. But the designers chose not to do that.

Now, I've written an extension method that can verify that this is the case, and it's no big deal to invoke it to ensure that that's the case, but I have to use reflection to do it (with all its performance penalties and whatnot).

So again, what compelling reason is there that could possibly have driven the decision to not have a checked enum?

For reference, from the MSDN documentation for the Enum class:

When you define a method or property that takes an enumerated constant as a value, consider validating the value. The reason is that you can cast a numeric value to the enumeration type even if that numeric value is not defined in the enumeration.

+2  A: 

If they are range checked, how would you have [Flags] enums and combine them using bitwise or?

An example would be ControlStyles enum

Mehrdad Afshari
One would think that the presence of the [Flags] attribute would suppress that behavior, since range-checking is a run-time behavior.
Mike Hofer
No, of course, I didn't mean it by any means. I thought it might help explaining my point.
Mehrdad Afshari
A: 

This is because (Foo)5 is Foo.Eenie | Foo.Moe

pablito
This would only be the case when [Flags] is used. Now you can also say that (Foo)5 is Foo.Meenie | Foo.Miney.
Ruben
yes it can also be Foo.Meenie | Foo.Miney, but this is not the compiler's problem, maybe you meant it to work it that way, Mike Hofer only asked why isn't this a compilation error. Of course that you can use [Flags] or use other 2 exp values.
pablito
Yes, but, in allowing Enum and Flags to be the same type, C# is perpetuating a design mistake from C.
Anthony
+4  A: 

Range-checking has a potentially unnecessary cost. It's therefore reasonable to not perform it implicitly. As already mentioned, [Flags] require that no such checking takes place. If the runtime would check for the presence of [Flags], this would still incur a runtime penalty every time you perform a conversion.

The only way around this would be for the compiler to be aware of the [Flags] attribute. I guess this wasn't done to reduce the amount of runtime knowledge hardcoded into the compiler.

Konrad Rudolph
This answer is correct, but as Konrad says, JaredPar's answer (http://stackoverflow.com/questions/432937/-net-why-arent-enums-range-value-checked/433084#433084) is the canonical answer.
Scott Dorman
A: 

I can think only of performance reasons

Michael Buen
A: 

You can always use Enum.IsDefined if you want to do the range checking yourself.

Jon B
.. but beware of the performance hit.. IsDefined uses Reflection.
Bjorn Reppen
A: 

I'd say the reason is because the enums are only type checked and in-lined by the compilier at compile time. Especially useful for extending enums with the Flags attribute (as it suddenly becomes forward compatible...

Rowland Shaw
+4  A: 

The issue was performance. It is quite simple to have a checked enum for normal enums such as Color

enum Color {
  Red,
  Blue
}

The problem though is for enum's which are used as bit flags.

enum Property {
  IsFirst = 0x1,
  IsDefault = 0x2,
  IsLastAccessed = 0x4
}

Having to do a bitwise check for every single integer which is converted to an Enum value was deemed to be too expensive. Hence the relaxed conversion to enum values.

JaredPar
This should be accepted as the canonical answer.
Konrad Rudolph
+1  A: 

Two reasons jump to mind. First, generating an out of range value requires a cast. If you intentionally cast, why would you expect to be slapped at runtime? It would have been far easier to disallow the cast.

Another compelling one is this one:

enum VeryHardToRangeCheck {
  one = 1,
  three = 3,
  five = 5
}
Hans Passant
+1  A: 

I can see two reasons:

  1. [Flags] work smoother without checks. For your example,

    (Foo)5 == Foo.Eenie | Foo.Moe;

  2. Enum is a value type. If you don't initialize it, it will be equal to zero. If you want to have checked enum values, it's not clear when exception should be thrown in this case - zero value may sneak to you code when you, for example, create an instance of a class containing this enum as a field.

    So current behavior is more consistent - you just know that you can have out-of-range values and check them.

Moreover, you should always explicitly do your checks and throw exceptions for values you can't process. Otherwise addition of new values to your enumeration may change behavior of your existing code. Fortunately is you use a single switch statement is a method which returns value, compiler will make you explicitly specify what you want to do if no matches found - in default section of switch of after it you will have to return a value or throw an exception. NotSupportedExcpetion of preferred in most cases.

Konstantin Spirin
A: 

Microsoft's C# Programming Guide specifically says not to do what you are asking about:

It is possible to assign any arbitrary integer value to meetingDay. For example, this line of code does not produce an error: meetingDay = (Days) 42. However, you should not do this because the implicit expectation is that an enum variable will only hold one of the values defined by the enum. To assign an arbitrary value to a variable of an enumeration type is to introduce a high risk for errors.

int is really only the storage type. In fact, it's possible to specify other integer storage types, like byte:

enum Days : byte {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};

The storage type determines how much memory an enum uses.

JeffH
A: 

Sorry to necro. Is it possible that you're confusing an Enum with a key-value collection here?

When you assign an integer Meenie=2 to an enum item, all you're doing is saying that Meenie is the 3rd index, and everything after it, if not specified, will take an index of 2+(distance from Meenie). So when you look for Foo[5], you're looking for index 5, not some key which has 5 as a value.

In most cases you wouldn't do this anyway; you'd ask for Foo.Meenie - that's the point of the enum, to set a known range of values and then refer to them by their exposed names. It's just a developer convenience. There are better structures out there for doing what you do in your example.

Superstringcheese
I have no idea whatsoever how enumerations correlate to key-value pairs in this context. An enumeration is an enumeration: a set of strongly typed constants. Key-value pairs are altogether different (think, a hash table or dictionary), typically designed for fast searching. My point, in the original post, is that an enumeration could be range checked in some way, but others have pointed out why that's not the case.
Mike Hofer