views:

190

answers:

5

I am wondering why it is that a single implicit conversion to an enum value doesn't work the same way it would if the conversion were to a system type. I can't see any technical reason however maybe someone smarter than I can shed some light for me.

The followoing fails to compile with, "A value of an integral type expected" and "Cannot implicitly convert type 'Test.En' to 'Test.Foo".

void test1 (){
    Foo f = new Foo();

    switch (f)         // Comment this line to compile
    //switch ((En)f)   // Uncomment this line to compile
    {
        case En.One:
            break;
    }
}


//////////////////////////////////////////////////////////////////

public enum En
{
    One,
    Two,
    Three,
}

public class Foo
{
    En _myEn;

    public static implicit operator En(Foo f)
    {
        return f._myEn;
    }
}

edit from the spec:

The governing type of a switch statement is established by the switch expression. If the type of the switch expression is sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or an enum-type, then that is the governing type of the switch statement. Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the type of the switch expression to one of the following possible governing types: sbyte, byte, short, ushort, int, uint, long, ulong, char, string. If no such implicit conversion exists, or if more than one such implicit conversion exists, a compile-time error occurs.

To Clarify the question, why is an enum-type not included with the list of allowed user-defined implicit conversions?

A: 

Take a look at that second error message. The compiler is trying to type-coerce the enum to match the type of what is in the switch statement.

As a point of interest, how does this fare?

void test2 (){
    Foo f = new Foo();

    switch (En.One)
    {
        case f:
            break;
    }
}
Anon.
This will fail since case must be followed by a constant-expression.
Courtney de Lautour
+2  A: 

Because enums are treated as integers for the purpose of switching, and as i've asked before, the compiler doesn't do multiple implicit conversions to get to a usable type, it can't figure out how to switch on foo.

My only theory as to why enums can't be used like that is that enums are not an integer type in and of themselves, and thus the compiler would have to do multiple implicit conversions to get to an integer primitive from foo.

I compiled then reflected your code and here's the results:

public static void Main()
{
    Foo f = new Foo();
    f._myEn = En.Three;
    switch (f)
    {
        case En.One:
        {
        }
    }
}

So apparently under the covers it does do an implicit conversion. :S

RCIX
"Because enums are treated as integers for the purpose of switching" - if you believe there is an actual conversion (in terms of language semantic, not implementation details of VC#) performed there, then please cite the spec.
Pavel Minaev
I can't find anything in the spec about it, but it's my best *guess*. Reflector gave me some interesting stuff when decompiling this, and i'll post my results on that.
RCIX
Do you mean when you reflect the compiled version, using an explicit cast within the switch "switch ((En)f)", it looks like that?
Courtney de Lautour
Yup. Strange isn't it?
RCIX
(i added the foo's enum assignment)
RCIX
When I use reflector the cast is still there. Although when looking at the IL I can't see a second conversion (from En to int) - which indicates to me that only a single conversion is called, and we know C# can handle that.
Courtney de Lautour
Quite odd, i'm not sure then. We may have to wait for The Eric or the Skeet ;)
RCIX
A: 

What if your class contained two enums and had implicit conversion operators for both? Or better yet, what if you had implicit conversion operators for an enum and int? Which conversion would the compiler "automatically" pick for you when you write a switch statement?

You have to explicitly specify what type of object is being used inside the switch statement. Implicit operators just tell the compiler/runtime "if you have a Foo and need an En, this code does that". It does not change the actual underlying type of the object.

scwagner
I would expect compilation to fail, On the grounds of '...exactly one user-defined implicit conversion (§6.4) must exist...' being violated.
Courtney de Lautour
A: 
void test1 (){
   Foo f = new Foo();
   En n = f;

    switch (n)
    {
        case En.One:
            break;
    }
}

EDIT: Since switch expects an integral value, writing switch(f) makes the compiler look for conversion from an instance of Foo to an integral type, which doesn't exist.

shahkalpesh
+3  A: 

The language design notes archive does not provide a justification for this decision. This is unfortunate, since the decision was changed. As you can see, the design evolved over time:

Notes from May 26th, 1999:

What types are allowed in as the argument to a switch statement? integral types including char, enum types, bool. C# also permits types that can be implicitly and unambiguously converted to one of the aforementioned types. (If there are multiple implicit conversion, then its ambiguous and a compile-time error occurs.) We're not sure whether we want to support string or not.

June 7th, 1999:

We discussed enabling switch on string arguments. We think this is a good feature – the language can add value by making this common case easier to write, and the additional complexity for the user is very low.

December 20th, 1999:

It is illegal to switch on an expression of type bool. It is legal to switch on an expression of an integral type or string type. It is legal to switch on an expression of a type that has exactly one implicit conversion to an integral type or string type.

Here we have the first occurence of the rule in question. Enums seem to have disappeared. And why not use user-defined implicit conversions to enum? Was this simply an oversight? The designers did not record their thoughts.

Note that the first sentence is NOT what we implemented. It is unclear to me why the implementors did the opposite of what the design committee recommended. This comes up again in the notes several years later:

August 13, 2003:

The compiler allows switch on bool. Don’t want to document this and add it to the language. Don’t want to remove it for compatibility reasons. Decided to silently continue to support switch on bool.

I decided that this was silly; when we produced the annotated print edition of the C# 3.0 specification, I added bool (and bool?) to the list of legal governing types.

In short: the whole thing is a bit of a mess. I have no idea why enums were in, then out, then half-in-half-out. This might have to remain one of the Mysteries of the Unknown.

Eric Lippert
Just out of curiosity, why does the C# compiler turn the explicit conversion into an implicit conversion when writing out the MSIL instead of an explicit one like the user writes?
jasonh
I don't understand the question. Can you give an example? Perhaps opening a new question would be appropriate.
Eric Lippert
Well, if you compile the following: `switch((En)f)`, the compiler will emit the following IL instruction `call valuetype Test/En Test/Foo::op_Implicit(class Test/Foo)`. My question is, why does the compiler choose to use an implicit conversion IL instruction instead of an explicit one, like the user wrote: `switch((En)f)`
jasonh
Again, I don't understand the question. Which other conversion operator would you like us to call in that case?
Eric Lippert
A complete example would be helpful. Why not open another question?
Eric Lippert
I figured it out. An Enum is not a type that would be able to use a castclass instruction. I feel so stupid... Sorry! :P
jasonh