Why can I assign 0.0 to enumeration values, but not 1.0
It's a bug that you can use 0.0. The compiler implicitly treats all constant expressions with a value of zero as just 0.
Now, it's correct for the compiler to allow an implicit conversion from a constant int
expression of 0 to your enum as per section 6.1.3 of the C# 5 specification:
An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type and to any nullable-type whose underlying type is an enum-type. In the latter case the conversion is evaluated by converting to the underlying enum-type and wrapping the result (§4.1.10).
I've spoken with the C# team about this before: they'd have liked to have removed the accidental conversion from 0.0 (and indeed 0.0m and 0.0f) to enum values, but unfortunately I gather it broke too much code - even though it should never have been allowed in the first place.
The Mono mcs
compiler prohibits all of these floating point conversions, although it does allow:
const int Zero = 0;
...
SomeEnum x = Zero;
despite the fact that Zero
is a constant expression but not a decimal-integer-literal.
I wouldn't be surprised to see the C# specification change in the future to allow any integer constant expression with a value of 0 (i.e. to mimic mcs
), but I wouldn't expect the floating point conversions to ever officially be correct. (I've been wrong before about predicting the future of C#, of course...)
enum is really intended (in all languages which support it) to be a way to work with meaningful and unique strings (labels) rather than a numeric values. Thus, in your example, you should only be using Bar and Baz when dealing with a Foo enumerated data type. You should never use (compare to, or assign) an integer, even though many compilers will let you get away with it (enums are usually integers internally), and in this case, a 0.0 is carelessly treated as a 0 by the compiler.
Conceptually, it should be all right to add an integer n to an enumerated value, to get n values further down the line, or to take val2-val1 to see how far apart they are, but unless the language specification explicitly allows this, I'd avoid it. (Think of an enumerated value as being like a C pointer, in the ways you can use it.) There's no reason enums couldn't be implemented with floating point numbers, and a fixed increment between them, but I haven't heard of this being done in any language.
Enumerations in C# are by definition integral values. For consistency C# shouldn’t accept either of these assignments, but 0.0
is silently treated as integral 0
. This is probably a holdover from C, where the literal 0
was treated specially and could essentially take any given type – integer, floating point number, null pointer … you name it.
Jon's answer is correct. I would add to it the following points.
I caused this silly and embarrassing bug. Many apologies.
The bug was caused by me misunderstanding the semantics of an "expression is zero" predicate in the compiler; I believed it was checking only for integer zero equality, when in fact it was checking for more along the lines of "is this the default value of this type?" In fact, in an earlier version of the bug it was actually possible to assign the default value of any type to an enum! It is now only default values of numbers. (Lesson: Name your helper predicates carefully.)
The behaviour I was attempting to implement that I messed up was in fact a workaround for a slightly different bug. You can read the whole terrible story here: https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-one and https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-two (Lesson: It is very easy to introduce new worse bugs while fixing old ones.)
The C# team decided to enshrine this buggy behaviour rather than fixing it because the risk of breaking existing code for no compelling benefit was too high. (Lesson: get it right the first time!)
The code I wrote in Roslyn to preserve this behaviour can be found in the method
IsConstantNumericZero
in https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs -- see it for more details of what exactly the Roslyn behaviour is. I wrote almost all the code in the Conversions directory; I encourage you to read all of it as there are many interesting facts about how C# diverges from the specification in the comments. I decorated each with SPEC VIOLATION to make them easy to find.
One more point of interest: C# also allows any enum value to be used in an enum initializer regardless of its zeroness:
enum E { A = 1 }
enum F { B = E.A } // ???
The spec is somewhat vague as to whether this should be legal or not, but again, as this has been in the compiler for a long time, the new compilers are likely to maintain the behaviour.