5

When reading Jon Skeet's answer to this particular question How can I make it so my class variables can only be set to one of three choices? y learned something new I was not aware of in C# and I guess the CLR type system.

What is the reasoning behind this being legal:

public enum Tests
{
    Zero = 0,
    One,
    Two
}

var nonExistantTestsValue = (Tests)1000;

This will compile perfectly fine but I fail to see the reason of why it should be so. The whole point of enumerations as far as I can see is to limit to a certain number of options the value of a given variable of the specified type.

If the limitation imposed by the enum definition is so easily broken, what is the point (besides the readability issue)? You can obviously make sure it is a valid value using reflection but why isn't this done at compile time?

There is probably a good reason for this to work the way it does but I fail to see it.

Community
  • 1
  • 1
InBetween
  • 32,319
  • 3
  • 50
  • 90
  • possible duplicate of [Why does casting int to invalid enum value NOT throw exception?](http://stackoverflow.com/questions/6413804/why-does-casting-int-to-invalid-enum-value-not-throw-exception) – nawfal Dec 01 '13 at 19:37

5 Answers5

5

Enumerations are essentially unique types that allow you to assign symbolic names to integral values. They are not used for restricting the allowable values of a variable...

Akram Shahda
  • 14,655
  • 4
  • 45
  • 65
  • 2
    I guess my understanding of enums was way off. I now understand the reason why it is as it is, specially with them being used as flags. Still I think it would have been a nice option in the type sytem to have some kind of enumeration that could impose option restriction (via attributes if possible or some other way). This means every time I have a enum argument that is not intended to be used as a flag I have to check if its valid. What you win in readability using enums you also partially loose in code clutter validating them. – InBetween Jul 11 '11 at 13:03
  • @InBetween: Then that would go against convenience, which is what @sehe is getting at. –  Jul 11 '11 at 13:12
  • @0A0D: yeah I understand that now. That is why I was just musing about an option to be able to do so in case it were of some benefit. Anyhow, its not an option for all the good reasons already posted here. – InBetween Jul 11 '11 at 13:16
3

If the limitation imposed by the enum definition is so easily broken, what is the point

I think the enum abstraction was not designed with limitation or guarantees in mind. I think it was designed with convenience and maintainability in mind

Good reasons:

Skip the first bullet if you don't want to see simple truths rigth now

  • the language specification [the reason I mention this is to remind people about the limited use of debating facts; a phrase like ... then what's the point triggers this for me]

  • performance (its hard/impossible to tell when a validation would not needed and this would hamper performance in a big way for specific applications

(Remember CLR functions can be called from anywhere, not just C#, not just your assembly)

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks for the answer. 1) Yeah, otherwise it would be a bonafide bug. I had figured that one out. 2) Yes I understand this could be the case, but why was it allowed (not c#) in the first place? It could be a simple compile time check with no cost at all in any language. – InBetween Jul 11 '11 at 12:44
  • +1 for the last sentence. I think the OP doesn't understand that casting is dangerous and should be avoided. – Ritch Melton Jul 11 '11 at 12:46
  • 3
    Calling the language specification a reason is a circular argument. – CodesInChaos Jul 11 '11 at 12:47
  • I don't see the reason why you can still cast 1000 into type `Tests` by your answer. –  Jul 11 '11 at 12:49
  • @CodeInChaos: thanks for pointing it out. Sometimes it doesn't hurt to state the (obvious) truth – sehe Jul 11 '11 at 12:52
  • @Ritch Melton: What does the dangers of casting have to do with anyhting? I am just curious about the reasons behind why things behave the way they do. My experience with the CLR and C# specifically is very short and I still have some very alarming grey areas. I dont like swallowing rules up without understanding why they are so. – InBetween Jul 11 '11 at 12:55
  • I don't consider a reason "because the specs says so". Also the water is wet because God wanted so? – Mario Vernari Jul 11 '11 at 13:03
  • @Mario, others: edited by popular demand (a.k.a. [ADD-optimizing](http://en.wikipedia.org/wiki/ADHD_predominantly_inattentive) the answer ordering?) – sehe Jul 11 '11 at 13:10
  • @InBetween - This is one. You are casting the int to an enum with an invalid value. I always read a cast as saying, "I'm smarter than the compiler's type checking at this point, so I need to turn it off for a moment." – Ritch Melton Jul 11 '11 at 14:51
  • @Ritch Melton: I think you misunderstood my question. I am not advocating doing this, I am asking *why* it is legal to do so. I would very much prefer that `(Tests)1000` would throw but I have received sufficient answers with valid arguments of why it is not so. The problem is that this type of cast *is* legal and therefore everytime you use a non flaggable enum as a method argument you can not rely on the value being valid (someone could just do `Foo((Tests)1000);`) and you are obliged to validate it using reflection, something that I was not aware of. – InBetween Jul 11 '11 at 15:09
  • @InBetween - I think I do/did misunderstand. I read the question as, 'Why can I do this dangerous cast thing?'. – Ritch Melton Jul 11 '11 at 16:35
2

It is more questioning in what you can do when enums are not limited to there values:

Flags is one of these examples:

[Flags]
enum MyFlag
{
     a,
     b,
     c
}

Now you can do bit operations:

MyFlag flags = MyFlag.a|MyFlag.b;
Peter
  • 27,590
  • 8
  • 64
  • 84
  • This is a really good reason, thanks. Who the hell downvoted this answer and peer's?!? – InBetween Jul 11 '11 at 13:06
  • [Flags] changes the semantics of enum to the compiler - If an enum is [flag]ged, then the compiler should emit runtime checks to validate the converted value against the possible bit masks produced by a,b and c. – Nordic Mainframe Jul 11 '11 at 13:09
  • @Luther Blissett: As far as I can tell, it does not. – InBetween Jul 11 '11 at 13:25
1

It is allowed any value because you may mark the enum with the "Flags" attribute. That means you may compose any value by OR-ing various members of the enum itself. Basically the compiler is not smart enough to take care of any possible way where the enum is used.

EDIT: found a previous post by Eric Lippert:

Why does casting int to invalid enum value NOT throw exception?

Community
  • 1
  • 1
Mario Vernari
  • 6,649
  • 1
  • 32
  • 44
  • thanks! Funny, seems I feel the same way Eric does in that it would have been nice to have a mechanism to ensure option cohersion. And the reason why enums behave the way they do seems to be, or so Eric claims (which is more than enough for me), that enums can be used as flags. – InBetween Jul 11 '11 at 13:30
  • @InBetween: cohersion must be a marriage of _[cohesion](http://en.wikipedia.org/wiki/Cohesion)_ and _[coercion](http://en.wikipedia.org/wiki/Coercion)_, then. Anyways, Eric Lippert is authoritative source on this one. – sehe Jul 11 '11 at 15:21
  • @sehe: lol, I should stick to words I really know how to write :p. Risking when it's not your native language is usually not a good idea... – InBetween Jul 11 '11 at 15:25
1

I don't know about the reasons for this design decision, but we can look at a few of its consequences.

Let's look at the IL representation of an enum:

.class private auto ansi sealed MyEnum
    extends [mscorlib]System.Enum
{
    // Fields
    .field public specialname rtspecialname int32 value__
    .field public static literal valuetype MyEnum Value0 = int32(0)
    .field public static literal valuetype MyEnum Value1 = int32(1)
    .field public static literal valuetype MyEnum Value2 = int32(2)

}

First we note that it is a value type and thus (MyEnum)0 must be valid. Second we see that the possible values of the enums are just consts and that enums at the runtime level are assignment compatible to integer literals.

Enum constants generally become an integer literal. So if you wanted to prevent invalid enums from appearing you'd need to introduce either expensive runtime checks when converting from an enum, or non trivial cross assembly load time checks to make sure enum literals baked into another assembly are valid.


Another thing is that it is possible to create enums backed by a long. But one property of longs is that their assignment is not guaranteed to be atomic. So guaranteeing that the value of a long based enum is valid is hard.

enum MyLongEnum:long
{
  Value1=0x0101010102020202,
  Value2=0x0303030304040404
}

If you assigned to such an enum from multiple threads you can end up with a mixed value that's invalid even if you never assigned an invalid value.


There is also an easy workaround to get safe enums: Use a class with a private constructor and static readonly fields or properties for the possible values. This way you lose integer conversions, literals and non-nullability, but gain type safety and better versioning.

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • Thanks! Good points. And the idea of a static class to ensure option restriction seems good enough. That is the horribly mistaken idea I had of how not flaggable enums behaved. – InBetween Jul 11 '11 at 13:23