views:

136

answers:

2

Hi, today I discovered a very strange behavior with C# function overloading. The problem occurs when I have a method with 2 overloads, one accepting Object and the other accepting Enum of any type. When I pass 0 as parameter, the Enum version of the method is called. When I use any other integer value, the Object version is called. I know this can be easilly fixed by using explicit casting, but I want to know why the compiler behaves that way. Is this a bug or just some strange language rule I don't know about?

The code below explains the problem (checked with runtime 2.0.50727)

Thanks for any help on this, Grzegorz Kyc

class Program
{
    enum Bar
    {
        Value1,
        Value2,
        Value3
    }

    static void Main(string[] args)
    {
        Foo(0);
        Foo(1);
        Console.ReadLine();
    }

    static void Foo(object a)
    {
        Console.WriteLine("object");
    }

    static void Foo(Bar a)
    {
        Console.WriteLine("enum");
    }
}
+9  A: 

It may be that you're not aware that there's an implicit conversion from a constant1 of 0 to any enum:

Bar x = 0; // Implicit conversion

Now, the conversion from 0 to Bar is more specific than the conversion from 0 to object, which is why the Foo(Bar) overload is used.

Does that clear everything up?


1 There's actually a bug in the Microsoft C# compiler which lets it be any zero constant, not just an integer:

const decimal DecimalZero = 0.0m;

...
Bar x = DecimalZero;

It's unlikely that this will ever be fixed, as it could break existing working code. I believe Eric Lippert has a two blog posts which go into much more detail.

The C# specification section 6.1.3 (C# 4 spec) has this to say about it:

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).

That actually suggests that the bug isn't just in allowing the wrong type, but allowing any constant 0 value to be converted rather than only the literal value 0.

EDIT: It looks like the "constant" part was partially introduced in the C# 3 compiler. Previously it was some constant values, now it looks like it's all of them.

Jon Skeet
thank you, that explains everything I wanted to know.
Grzegorz Kyc
The posts you're looking for are http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/the-root-of-all-evil-part-one.aspx and http://blogs.msdn.com/b/ericlippert/archive/2006/03/29/the-root-of-all-evil-part-two.aspx. In allowing the compiler to use any constant zero I accidentally caused it to use any constant zero of any type, not just the integer types. I regret all the errors that led to this mess.
Eric Lippert
Excellent, thanks - will edit those in.
Jon Skeet
+2  A: 

I know I have read somewhere else that the .NET system always treats zero as a valid enumeration value, even if it actually isn't. I will try to find some reference for this...

OK, well I found this, which quotes the following and attributes it to Eric Gunnerson:

Enums in C# do dual purpose. They are used for the usual enum use, and they're also used for bit fields. When I'm dealing with bit fields, you often want to AND a value with the bit field and check if it's true.

Our initial rules meant that you had to write:

if ((myVar & MyEnumName.ColorRed) != (MyEnumName) 0)

which we thought was difficult to read. One alernative was to define a zero entry:

if ((myVar & MyEnumName.ColorRed) != MyEnumName.NoBitsSet)

which was also ugly.

We therefore decided to relax our rules a bit, and permit an implicit conversion from the literal zero to any enum type, which allows you to write:

if ((myVar & MyEnumName.ColorRed) != 0)

which is why PlayingCard(0, 0) works.

So it appears that the whole reason behind this was to simply allow equating to zero when checking flags without having to cast the zero.

jdmichal