views:

164

answers:

2

I'm wrestling with a weird, at least for me, method overloading resolution of .net. I've written a small sample to reproduce the issue:

class Program
{
    static void Main(string[] args)
    {
        var test = new OverloadTest();
        test.Execute(0);
        test.Execute(1);

        Console.ReadLine();
    }
}

public class OverloadTest
{
    public void Execute(object value)
    {
        Console.WriteLine("object overload: {0}", value);
    }

    public void Execute(MyEnum value)
    {
        Console.WriteLine("enum overload: {0}", value);
    }
}

public enum MyEnum
{ 
    First = 1, Second = 2, Third = 3
}

Will print:

enum overload: 0
object overload: 1

Basically the overload called is different depending on the value (0, 1) instead of the given data type.

Could someone explain?

Update

I should have pointed out that there's a different behaviour between C# 2 and C# 3

Do((long)0) => object overload //C# 2
Do((long)0) => enum overload   //C# 3 
A: 

An Enum is just mapped to an int (by default). 0 does not map to your Enum so the overload that takes an object is used. 1 maps to your enum so the Enum overload is used.

You could do this:

Execute((object) 1);

to output

object overload: 1

statichippo
It looks like the results are actually the opposite of what you'd expect. The enum overload is getting hit for 0 and not for 1.
mkedobbs
I just went over this and realized I had misread the post. I thought that 0 was calling "object" and 1 was calling "enum". I'll have to look into this because _that_ does not make much sense!
statichippo
@statichippo: It makes perfect sense when you look at the spec and what conversions are available :)
Jon Skeet
The question was for the reason why the behaviour is just **not** like the way you described it.
Doc Brown
+11  A: 

Yes - the constant 0 is implicitly convertible to any enum type. The constant 1 is only explicitly convertible to the enum type. Both are implicitly convertible to object (via boxing) but the conversion to the enum is preferred where it's available.

Note that this has nothing to do with what values the enum defines. The conversion for any non-zero value is explicit whether it matches a value in the enum or not. It's just a special case for the value 0, which makes some other code simpler (particularly when dealing with flags). I don't have the spec on hand to find the reference, I'm afraid.

Bonus strangeness: due to a bug in the MS compiler (never to be fixed - it would break backward compatibility) it's actually various zero constants, not just an integer. So Execute(0d) and Execute(0m) will convert a double and a decimal to the enum too. It doesn't work for every zero constant - it depends on the exact nature of the source code. It's all very odd - follow the link where Eric Lippert reveals all...

Jon Skeet
That is very strange. I would have expected it not to compile at all, because the two separate calls to Execute are ambiguous. 0 and 1 are int's, and so it is equally valid to call the object overload as well as the MyEnum overload.
Nick
i did not know that. why is 0 implicitly convertible to any enum type? in the example here, 0 is not a valid value. if something doesn't make sense to my line of thinking, that doesn't necessarily mean all that much though (as indicated 1 minute ago) ;)
statichippo
@statichippo: It's handy in a few cases, such as after performing bitwise arithmetic on flags. @Nick: Both methods are *applicable*, but the conversion from 0 to enum is "better than" the conversion from 0 to object by the rules of the C# spec... which is why that overload is called.
Jon Skeet
it gets even stranger than that as constants that evaluate to 0 were sometimes implictly castable to enum and sometimes not http://blogs.msdn.com/ericlippert/archive/2006/03/29/the-root-of-all-evil-part-two.aspx
jk
http://www.jaggersoft.com/csharp_standard/13.1.3.htm -- "An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type."
statichippo
@jk: Ooh, I'd forgotten the "sometimes not" bit. Will add reference to answer.
Jon Skeet
"In the example here, 0 is not a valid value" - not really, since for an `enum`, _any_ value of the underlying type (`int` in this case) is a valid value of enum type as well. It's just that some of those values have symbolic names, but most do not. The reason to single out `0` is mainly because it's the default value for any enum type, whether you have an enum member mapped to 0 or not, and is used where default values are normally used (e.g. to default-initialize fields etc). Of course you can also write `default(Foo)`, but that only appeared in 2.0; and `new Foo()` isn't very obvious.
Pavel Minaev
Nick, it is not equally valid to call the object overload as the enum overload. With the call with (1), there is no choice; the constant 1 does not convert implicitly to enum. With the call with (0), there is a choice; either is good. In the situation where either is good, the one that we pick is the *more specific type*. Since every enum goes to object, but not every object goes to an enum, an enum must be the more specific type, so we choose it over object.
Eric Lippert