Why do enum values req explicit conversions

  • Thread starter Thread starter Michael C
  • Start date Start date
Dale,

Consider this code:

class App
{
static void Main(string[] args)
{
Foo f = Foo.One;
int i = (int)f;
}
}
enum Foo
{
One,
Two
}

if you look at the IL for Main you see:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 5 (0x5)
.maxstack 1
.locals init ([0] valuetype Foo f,
[1] int32 i)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: stloc.1
IL_0004: ret
} // end of method App::Main


Notice there is no unbox instruction. So your assertion that the "cast" is actually an unbox operation is incorrect. The cast operator is abused in several situations. Its used for type coercion of reference types; its used for numeric conversion of value types and its used for boxing and unboxing. The enum is a value type with an associated numeric value. This is held in the value__ member of the enum and is typed to the type after the colon in the enum definition (or int32 if its omitted).

Regards

Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk


There is no value type of enum. There is an underlying TypeCode that is
used for integral representation of the enum. To get the value type, you
must unbox it. If it were already an int, it wouldn't be necessary to cast
an int as an int. But if you box an int, it is necessary to unbox to get
back to the int. The syntax for unboxing is, coincidentally, the same as
the syntax for an explicit cast.
 
Well, I certainly don't mean to be argumentative about it or to go down with
the ship defending a point that I can't seem to prove [smiling].

I have never disassembled a boxing or unboxing operation to see what it
would look like so I don't know how to respond to what you say. I only know
that an enum is definitely an instance of System.Enum - proven by the fact
that it inherits the instance methods of System.Enum, and that System.Enum
is a reference type, as documented in the C# and .Net Framework
documentation.

I suppose the most important issue is that the syntax of the cast operator
must be used to solve the OP's original question. Whether it is a cast or
an unboxing doesn't make any difference in the end.

It has certainly been an interesting and entertaining discussion though.

Dale


Richard Blewett said:
Dale,

Consider this code:

class App
{
static void Main(string[] args)
{
Foo f = Foo.One;
int i = (int)f;
}
}
enum Foo
{
One,
Two
}

if you look at the IL for Main you see:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 5 (0x5)
.maxstack 1
.locals init ([0] valuetype Foo f,
[1] int32 i)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: stloc.1
IL_0004: ret
} // end of method App::Main


Notice there is no unbox instruction. So your assertion that the "cast"
is actually an unbox operation is incorrect. The cast operator is abused in
several situations. Its used for type coercion of reference types; its used
for numeric conversion of value types and its used for boxing and unboxing.
The enum is a value type with an associated numeric value. This is held in
the value__ member of the enum and is typed to the type after the colon in
the enum definition (or int32 if its omitted).
 
Dale Preston said:
There is no value type of enum. There is an underlying TypeCode that is
used for integral representation of the enum. To get the value type, you
must unbox it. If it were already an int, it wouldn't be necessary to cast
an int as an int. But if you box an int, it is necessary to unbox to get
back to the int. The syntax for unboxing is, coincidentally, the same as
the syntax for an explicit cast.

No, value types *are* enums.
System.Int32 is a value type inheriting from System.ValueType. System.Enum
is a reference type inheriting from System.ValueType. Since we seem to
agree that System.Enum is a reference type, yet it inherits from a value
type, then how did it get to be a reference type? As I first quoted from
the C# specification, it is boxed at runtime. That's the only way to get a
reference type out of a value type.

No, System.Enum doesn't inherit from a value type. It inherit from a
reference type, System.ValueType.

An enum value is only boxed when it needs to be - when it's
passed/declared/used/whatever as System.Enum, System.ValueType or
System.Object - just as an int is only boxed when it's used as
System.ValueType or System.Object (or interfaces).
All specific enum instances are of type Enum. Subclasses are always of the
type of the superclass. Basic OOP.

No, an unboxed enum value *isn't* actually of type System.Enum. It's
just convertible to Enum by boxing - same as an int value isn't
actually of type System.ValueType unless it's boxed.
Also from the C# specification:

<quote>
The explicit enumeration conversions are:
[...]
From any enum-type to sbyte, byte, short, ushort, int, uint, long,
ulong, char, float, double, or decimal.
</quote>

There are a lot of explicit conversions possible in the .Net classes. They
do not imply inheritance by the existence of the explicit conversion. In
fact, since a subclass can always be substituted for a superclass, the
opposite could be inferred though it doesn't always hold true.

I've never claimed there's inheritance going on here - I'm claiming
that enum values aren't boxed unless they need to be, contrary to your
idea that they're *always* boxed.
Well, there has been argument that enum instances do not inherit from
System.Enum.

And indeed they don't, unless they're boxed.

From the CIL specification, section 8.9.10:
<quote>
Value Types, in their unboxed form, do not inherit from any type. Boxed
value types shall inherit directly from System.ValueType unless they
are enumerations, in which case they shall inherit from System.Enum.
Boxed value types shall be sealed.
</quote>

Note the first part carefully.
Again, if they do not, then how do they get the instance
methods of System.Enum?

They get them when they're boxed.
They do not get the instance methods of System.Int32.
Irrelevant.


But it wasn't.

Indeed - and type safety is the reason, not boxing.
 
Dale Preston said:
Well, I certainly don't mean to be argumentative about it or to go down with
the ship defending a point that I can't seem to prove [smiling].

I have never disassembled a boxing or unboxing operation to see what it
would look like so I don't know how to respond to what you say. I only know
that an enum is definitely an instance of System.Enum - proven by the fact
that it inherits the instance methods of System.Enum, and that System.Enum
is a reference type, as documented in the C# and .Net Framework
documentation.

By your logic, that would make *all* types reference types, as
System.Object and even System.ValueType are reference types.

The answer is that enums *don't* actually derive from System.Enum in
their unboxed form, just as (say) an Int32 doesn't actually derive from
System.ValueType in its unboxed form.
I suppose the most important issue is that the syntax of the cast operator
must be used to solve the OP's original question. Whether it is a cast or
an unboxing doesn't make any difference in the end.

It makes a *huge* difference! Enums would be much less efficient if
they were really reference types, just as all the primitive types would
be. It's very important that they really are value types.
 
Yes, but this was the OP's point.

If he writes the HasFlag method so that it takes int's as arguments,
then he can do the & operation inside the method and come up with a
true / false answer, but he has to call it like this:

if (HasFlag((int)enumValue, (int)Enumeration.EnumFlag)) { ... }

with the ugly (int) casts in there.

On the other hand, if he makes the arguments System.Enum, he doesn't
need an explicit cast (but suffers unwanted boxing), but worse yet he
can't do anything with the boxed values inside the method. C# won't let
him cast a System.Enum to an int, and he can't perform an & operation
on Enums, so he can't calculate the answer.

As I said, I think that the original syntax, as complex as it looks, is
the best way to go, but then that's a matter of taste. :)
 
Bruce Wood said:
Yes, but this was the OP's point.

If he writes the HasFlag method so that it takes int's as arguments,
then he can do the & operation inside the method and come up with a
true / false answer, but he has to call it like this:

if (HasFlag((int)enumValue, (int)Enumeration.EnumFlag)) { ... }

with the ugly (int) casts in there.

On the other hand, if he makes the arguments System.Enum, he doesn't
need an explicit cast (but suffers unwanted boxing), but worse yet he
can't do anything with the boxed values inside the method. C# won't let
him cast a System.Enum to an int, and he can't perform an & operation
on Enums, so he can't calculate the answer.

As I said, I think that the original syntax, as complex as it looks, is
the best way to go, but then that's a matter of taste. :)

In fact, with a bit of jiggery pokery, it can be done:

using System;

class Test
{
enum Foo
{
Bar,
Baz
}

static void Main()
{
long l1 = ConvertToLong (Foo.Bar);
long l2 = ConvertToLong (Foo.Baz);

Console.WriteLine (l1);
Console.WriteLine (l2);
}

static long ConvertToLong (Enum e)
{
object o = Convert.ChangeType(e,
Enum.GetUnderlyingType(e.GetType()));

return Convert.ToInt64 (o);
}
}

I dread to think of the performance penalties involved, but I believe
it should work for all enums *except* those with an underlying type of
ulong which have values greater then long.MaxValue in them. (You could
convert to ulong instead, but then you'd have trouble with enums with
negative values.)

I'm not suggesting this is a good idea, just thought I'd point it out
:)

Having looked again, here's a somewhat simpler (and more efficient)
implementation with the same limitation:

static long ConvertToLong (Enum e)
{
return ((IConvertible)e).ToInt64(null);
}

(With a bit more effort it would be possible to work round the
limitation to get it to return a negative value in case of overflow,
but I'm not going there just now...)
 
As I said, I think that the original syntax, as complex as it looks, is
the best way to go, but then that's a matter of taste. :)

It certainly is the best we have, but thats a failing in the language, IMHO,
;). One of the small handful of things I don't particularly like about the
language.
 
Daniel O'Connell said:
It certainly is the best we have, but thats a failing in the
language, IMHO, ;). One of the small handful of things I don't
particularly like about the language.

Out of interest, what would your preferred fix be? I certainly wouldn't
like the conversion to an int to be implicit, but I agree that there
ought to be some better way of handling this.

Out of interest, are generics going to help here? I haven't looked at
them in terms of enums yet.

(For a bit of fun, have a look at how the Java enum base class is
defined: "public abstract class Enum<E extends Enum<E>>". It always
takes me a while of thinking about it before I understand exactly what
it means :)
 
Jon Skeet said:
Out of interest, what would your preferred fix be? I certainly wouldn't
like the conversion to an int to be implicit, but I agree that there
ought to be some better way of handling this.

Perhaps I misread Bruces meaning or I'm misreading yours, but I figured he
was talking about
if ((SomeEnumValue & SomeEnum.SomeFlag) != 0)

just to make sure we are on the same page.

I agree implicitly converting to int is not the right answer. The compiler
would need to offer more complete support for enums and their logic. I've
been playing with syntax for flag enums to clarify them significantly for a
long time, but never really found anything I liked. The best I coudl come up
with was pseudo member methods

EnumType enum = <whatever>;

enum.IsSet(EnumType.A);

but that leaves a unpleasent taste of inconsistency and falseness in ones
mouth. Anything else would require either a new keyword or a new use for
one.

I thought about ridiculous ones like

if (Enum.Element is set in val)

or

if (is set(Enum.Element, val))

but those are a bit odd aswell. Any thoughts of your own? For that matter
does anyone know how Delphi deals with this? Or how java will?
Out of interest, are generics going to help here? I haven't looked at
them in terms of enums yet.

It doesn't appear so. Generics cannot use System.Enum as a constraint, so
something like
(For a bit of fun, have a look at how the Java enum base class is
defined: "public abstract class Enum<E extends Enum<E>>". It always
takes me a while of thinking about it before I understand exactly what
it means :)

Man, that is a bit of a twister isn't it. I'll have a closer look at it, I"m
not sure I can figure out the point without seeing the body, ;).
 
Daniel O'Connell said:
Perhaps I misread Bruces meaning or I'm misreading yours, but I figured he
was talking about
if ((SomeEnumValue & SomeEnum.SomeFlag) != 0)

just to make sure we are on the same page.
Yup.

I agree implicitly converting to int is not the right answer. The compiler
would need to offer more complete support for enums and their logic. I've
been playing with syntax for flag enums to clarify them significantly for a
long time, but never really found anything I liked. The best I coudl come up
with was pseudo member methods

EnumType enum = <whatever>;

enum.IsSet(EnumType.A);

but that leaves a unpleasent taste of inconsistency and falseness in ones
mouth. Anything else would require either a new keyword or a new use for
one.

I'm not sure it would need to be a "pseudo" method - what's wrong with
it being a real method?
I thought about ridiculous ones like

if (Enum.Element is set in val)

or

if (is set(Enum.Element, val))

but those are a bit odd aswell. Any thoughts of your own? For that matter
does anyone know how Delphi deals with this? Or how java will?

Java has an EnumSet type, but then its enums are rather different -
they're reference types, and are basically just a bit of compiler help
for switch statements and syntactic over the historic strongly typed
way of doing things. In general they're very nice though - it's good
being able to define methods on enums, and override them on a per case
basis.
It doesn't appear so. Generics cannot use System.Enum as a constraint, so
something like
IsFlagSet<T>(T enumValue, T flag) where T : System.Enum {}
won't work.
Right.


Man, that is a bit of a twister isn't it. I'll have a closer look at it, I"m
not sure I can figure out the point without seeing the body, ;).

There's a page somewhere explaining what it's all about, but it's
basically a way of saying "I'm E!" so that all enums can automatically
declare themselves to implement Comparable<the_same_enum> etc.
 
Jon Skeet said:
I'm not sure it would need to be a "pseudo" method - what's wrong with
it being a real method?

Primarily that I cannot change the runtime to require a given method but
could provide a language deriative that uses a psuedo-method. The current
runtime always pukes and dies when I try to generate an enum with a method,
so I assume it doesn't permit it(although I"m pretty sure the spec doesn't
disallow it exactly).

Assuming one could add methods to enums, you'd still have other languages to
contend with (VB might not, for example), so using a psuedo method would
boost consistency. The other primary argument would be the psuedomethod
would basically be a macro generating the equivilent logic code as we use
these days, just hidden behind cleaner syntax. A method would require
atleast a pair of stack loads, an invocation, then a pair of stack loads, an
and instruction, and a comparison, while a psuedo method could be
shortcutted to only a pair of stack loads, an and, and a compare. (The
compare might be shaved off both, I can't recall IL well enough off hand to
say for sure though)

My ideas are always primarily C# only and have to be put together without
extra runtime support. If MS or the ECMA decides they are good ones and
chose to modify the runtime or CLS to support them in cleaner fashion, then
more power to them. I just lack the muscle to pull that off.
There's a page somewhere explaining what it's all about, but it's
basically a way of saying "I'm E!" so that all enums can automatically
declare themselves to implement Comparable<the_same_enum> etc.

Ahh, much as I expected. I have a couple classes that are an awful lot like
that written myself to support making a generic tree's root a node as well.
 
Back
Top