Compiler resolves to wrong overloaded constructor or method

  • Thread starter Thread starter Philippe Bertrand
  • Start date Start date
P

Philippe Bertrand

Is this a bug in the C# compiler or CLR runtime?

enum MyEnum { ZERO = 0, ONE = 1, TWO = 2 }
class Foo {
public Foo(string,object) { ... }
public Foo(string,MyEnum) { ... }
}

Foo f = new Foo("", 0); // Uses Foo(string,MyEnum) constructor instead of
Foo(string,object)
Foo f = new Foo("", 0.0); // Uses Foo(string,MyEnum) constructor instead
of Foo(string,object)
Foo f = new Foo("", (int)0); // Uses Foo(string,MyEnum) constructor
instead of Foo(string,object)
BUT
Foo f = new Foo("", 1 ); // Uses Foo(string,object)
Foo f = new Foo("", (object)(int)0 ); // Uses Foo(string,object) with
object type Int32
Foo f = new Foo("", (double)0.0 ); // Uses Foo(string,object)

Actual case was from a ADO.NET Data Provider's PrvParameter constructors!
Same issue came up resolving the various overloads of
PrvParameterCollection.Add!

Anybody else seen this?
Philippe
 
Philippe,

Haven't seen it.

How do you know it's using the Foo(string,MyEnum) constructor when you do a
new Foo("",0)? Are you seeing this when you single-step through the code?
If not, you should single-step with the debugger to make sure what you think
is executing is really executing. If you single-step and *still* see this
behavior, then I would rule out a bug in the debug display (it's happened in
the past, in unpatched CLR 1.0) by putting DebugWriteLine() statements into
the constructors and making sure they really DO execute as the debugger
shows.

--Bob
 
Philippe Bertrand said:
Is this a bug in the C# compiler or CLR runtime?

enum MyEnum { ZERO = 0, ONE = 1, TWO = 2 }
class Foo {
public Foo(string,object) { ... }
public Foo(string,MyEnum) { ... }
}

Foo f = new Foo("", 0); // Uses Foo(string,MyEnum) constructor instead of
Foo(string,object)
Foo f = new Foo("", 0.0); // Uses Foo(string,MyEnum) constructor instead
of Foo(string,object)
Foo f = new Foo("", (int)0); // Uses Foo(string,MyEnum) constructor
instead of Foo(string,object)
BUT
Foo f = new Foo("", 1 ); // Uses Foo(string,object)
Foo f = new Foo("", (object)(int)0 ); // Uses Foo(string,object) with
object type Int32
Foo f = new Foo("", (double)0.0 ); // Uses Foo(string,object)


I reproduced it. Very odd, especially the difference in behavior between
0.0 and (double)0.0, since 0.0 is already a double.
 
hi philippe,

see 7.4.2.2 of the C# language specification.
Foo f = new Foo("", 0); // Uses Foo(string,MyEnum) constructor instead of
Foo(string,object)

there exists an implicit convertion between int and MyEnum.
see 6.1.3 of the C# lang specification.
Foo f = new Foo("", 0.0); // Uses Foo(string,MyEnum) constructor instead
of Foo(string,object)

there exists an implicit convertion between double and int
and MyEnum. see 6.1.3.
Foo f = new Foo("", (int)0); // Uses Foo(string,MyEnum) constructor
instead of Foo(string,object)

there exists an implicit convertion between int and MyEnum.
see 6.1.3.
BUT
Foo f = new Foo("", 1 ); // Uses Foo(string,object)

6.1.3 doesn't apply here.
Foo f = new Foo("", (object)(int)0 ); // Uses Foo(string,object) with
object type Int32

the cast forces Foo(string,object).
Foo f = new Foo("", (double)0.0 ); // Uses Foo(string,object)

the cast disables the implicit int promotion.
since no Foo(string,double) exists, the next bext
overloaded method is taken.

bye
rob
 
Robert,

Can you give correct links to C# spec? (I mean ECMA-334, 2nd ed. (december
2002)).

Alex.
 
But that is ridiculous!

You are saying to users that for all but these two special constants, the
language behaves on way but for these two magic constants you have to
explicitly cast.

Being in the spec doesn't make it a good idea!

Robert Jordan said:
hi philippe,

see 7.4.2.2 of the C# language specification.


there exists an implicit convertion between int and MyEnum.
see 6.1.3 of the C# lang specification.


there exists an implicit convertion between double and int
and MyEnum. see 6.1.3.


there exists an implicit convertion between int and MyEnum.
see 6.1.3.


6.1.3 doesn't apply here.

Why would there be an implicit conversion between int and MyEnum for the
value 0 but not the value 1?

Note:
int i = 0;
Foo f = new Foo("", i ); // Uses Foo(stirng,object)

This section 6.1.3 seems an extremely bizzard exception!
the cast forces Foo(string,object).


the cast disables the implicit int promotion.
since no Foo(string,double) exists, the next bext
overloaded method is taken.

Why would the implicit conversion be disabled when casting (double)0.0 but
not when casting (int)0 ?

Philippe
 
Yes, I'm certain (resulting object state different and stepped through
code). See my reply to Robert Jordan for cause of this magic behaviour.

Philippe
 
Hi Alex,
Robert,

Can you give correct links to C# spec? (I mean ECMA-334, 2nd ed. (december
2002)).

13.1.3 Implicit enumeration conversions
(6.1.3 MSDN spec)

"An implicit enumeration conversion permits the decimal-integer-literal
0 to be converted to any enum-type"


14.4.2.1 Applicable function member
(7.4.2.2 MSDN spec)

Rob
 
Hi Philippe,
You are saying to users that for all but these two special constants, the
language behaves on way but for these two magic constants you have to
explicitly cast.

There is no magic. The language designers wanted to avoid
constructs like "(SomeEnum)0", because enums may be
[Flags]-able, and thus they may be "0" w/out having
a literal with the value "0".

Consider this:

if ((val & SomeEnum.Foo) == 0) {
}

W/out this rule, the bool expression has to
be written like:

if ((val & SomeEnum.Foo) == (SomeEnum)0) {
}

This is ugly!
Being in the spec doesn't make it a good idea!

Reading the specs is a good idea, too ;-)

Bye
Rob
 
Robert Jordan said:
Hi Philippe,
You are saying to users that for all but these two special constants, the
language behaves on way but for these two magic constants you have to
explicitly cast.

There is no magic. The language designers wanted to avoid
constructs like "(SomeEnum)0", because enums may be
[Flags]-able, and thus they may be "0" w/out having
a literal with the value "0".

Much like Dennis Ritchie wanted to avoid introducing a keyword for the null
pointer, when "0" worked so well :-) That was a horrifically bad decision:
look at any newsgroup for C or C++ beginners: wheneve the subject arises
it's clear that 90% of them don't understand that a null pointer and the
number zero are wholly different objects that happen to be spelled the same.
Likewise, a special constant no_flags_set (or some such) that's a member of
all flags-enabled enums would be much clearer.
 
Mike Schilling said:
Robert Jordan said:
Hi Philippe,
You are saying to users that for all but these two special constants,
the
language behaves on way but for these two magic constants you have to
explicitly cast.

There is no magic. The language designers wanted to avoid
constructs like "(SomeEnum)0", because enums may be
[Flags]-able, and thus they may be "0" w/out having
a literal with the value "0".

Much like Dennis Ritchie wanted to avoid introducing a keyword for the
null pointer, when "0" worked so well :-) That was a horrifically bad
decision: look at any newsgroup for C or C++ beginners: wheneve the
subject arises it's clear that 90% of them don't understand that a null
pointer and the number zero are wholly different objects that happen to be
spelled the same. Likewise, a special constant no_flags_set (or some such)
that's a member of all flags-enabled enums would be much clearer.

I disagree. Adding an implicit value removes one possible value from all
enums AND it adds complexity to *EVERY* use of a flags based attribute
whereas 0 is only going to come into play in rare circumstances and it
removes the ability to provide a speciallly named NoFlags value.

Simply put, in virtually any situation where you try to pump in soemthing
you feel will be easier in one circumstance you will inevitably cause
complexity in other circumstances. In the null pointer circumstance, a null
keyword would probably be correct because null pointers are extremly
common(maybe the most common thing you'll see in non-arithmetic C), however
flagged enums are relativly rare and certainly don't warrent a keyword or
implicit behaviour of any kind.
 
Daniel O'Connell said:
Mike Schilling said:
Robert Jordan said:
Hi Philippe,

You are saying to users that for all but these two special constants,
the
language behaves on way but for these two magic constants you have to
explicitly cast.

There is no magic. The language designers wanted to avoid
constructs like "(SomeEnum)0", because enums may be
[Flags]-able, and thus they may be "0" w/out having
a literal with the value "0".

Much like Dennis Ritchie wanted to avoid introducing a keyword for the
null pointer, when "0" worked so well :-) That was a horrifically bad
decision: look at any newsgroup for C or C++ beginners: wheneve the
subject arises it's clear that 90% of them don't understand that a null
pointer and the number zero are wholly different objects that happen to
be spelled the same. Likewise, a special constant no_flags_set (or some
such) that's a member of all flags-enabled enums would be much clearer.

I disagree. Adding an implicit value removes one possible value from all
enums

That would be true if it were a qualified name (MyEnum.no_flags_set), not
if, as I suggested, it's a language keyword.
AND it adds complexity to *EVERY* use of a flags based attribute whereas 0
is only going to come into play in rare circumstances and it removes the
ability to provide a speciallly named NoFlags value.

Why would it either add complexity (if you don't use it) or remove your
abiality to add a 0-valued member?
Simply put, in virtually any situation where you try to pump in soemthing
you feel will be easier in one circumstance you will inevitably cause
complexity in other circumstances.
In the null pointer circumstance, a null keyword would probably be correct
because null pointers are extremly common(maybe the most common thing
you'll see in non-arithmetic C), however flagged enums are relativly rare
and certainly don't warrent a keyword or implicit behaviour of any kind.

The current implicit behavior (that in effect zero is a member of every
enum, at least under some spellings of zero) is what I'd like to avoid.
Introducing an *explicit* constant that represents "no flags set" for each
enum removes it. It also makes it clear to the uninitiated

1. that something unusual is happening, and
2. how to look it up (under "no_flags_set")

The current situation looks like a bizarre compiler error unless you're
familiar with the precise place in the spec that describes it.
 
Mike Schilling said:
Daniel O'Connell said:
Mike Schilling said:
Hi Philippe,

You are saying to users that for all but these two special constants,
the
language behaves on way but for these two magic constants you have to
explicitly cast.

There is no magic. The language designers wanted to avoid
constructs like "(SomeEnum)0", because enums may be
[Flags]-able, and thus they may be "0" w/out having
a literal with the value "0".

Much like Dennis Ritchie wanted to avoid introducing a keyword for the
null pointer, when "0" worked so well :-) That was a horrifically bad
decision: look at any newsgroup for C or C++ beginners: wheneve the
subject arises it's clear that 90% of them don't understand that a null
pointer and the number zero are wholly different objects that happen to
be spelled the same. Likewise, a special constant no_flags_set (or some
such) that's a member of all flags-enabled enums would be much clearer.

I disagree. Adding an implicit value removes one possible value from all
enums

That would be true if it were a qualified name (MyEnum.no_flags_set), not
if, as I suggested, it's a language keyword.

That is what I took you as meaning, yes.
Why would it either add complexity (if you don't use it) or remove your
abiality to add a 0-valued member?
Because if it is a member of a given enum, its plain silly. I would have
other issues with a special keyword.
The current implicit behavior (that in effect zero is a member of every
enum, at least under some spellings of zero) is what I'd like to avoid.
Introducing an *explicit* constant that represents "no flags set" for each
enum removes it. It also makes it clear to the uninitiated

It removes it at the cost of a new keyword(which users need to know). As it
stands there is only about three keywords that are very rarely used, yet
each has some value. Two of which, unsafe and fixed, make sense. The third,
stackalloc, could probably have been written as an API function or a
function of new, each other keyword has a practical common usage.
I don't think drawing a line between null pointers and unintialized enums is
a particularly valid point, either. As I showed before, null pointers are
more common and the constant 0 just isn't *that* common in code. In much of
my code there are no constants written into code, they instead are fields
which I reference(for clarities sake, I also tend to not use const,
prefering to take the potential performance hit and use readonly).
1. that something unusual is happening, and
2. how to look it up (under "no_flags_set")

The current situation looks like a bizarre compiler error unless you're
familiar with the precise place in the spec that describes it.

I would have prefered explicit casting, personally, but I don't think
learning the implicit case is terribly hard.
 
Daniel O'Connell said:
Mike Schilling said:
Daniel O'Connell said:
Hi Philippe,

You are saying to users that for all but these two special constants,
the
language behaves on way but for these two magic constants you have to
explicitly cast.

There is no magic. The language designers wanted to avoid
constructs like "(SomeEnum)0", because enums may be
[Flags]-able, and thus they may be "0" w/out having
a literal with the value "0".

Much like Dennis Ritchie wanted to avoid introducing a keyword for the
null pointer, when "0" worked so well :-) That was a horrifically bad
decision: look at any newsgroup for C or C++ beginners: wheneve the
subject arises it's clear that 90% of them don't understand that a null
pointer and the number zero are wholly different objects that happen to
be spelled the same. Likewise, a special constant no_flags_set (or some
such) that's a member of all flags-enabled enums would be much clearer.

I disagree. Adding an implicit value removes one possible value from all
enums

That would be true if it were a qualified name (MyEnum.no_flags_set), not
if, as I suggested, it's a language keyword.

That is what I took you as meaning, yes.

Then please explain how it removes a value from all enums.
Because if it is a member of a given enum, its plain silly. I would have
other issues with a special keyword.

Saying "it's silly" doesn't asnwer the question.
It removes it at the cost of a new keyword(which users need to know). As
it stands there is only about three keywords that are very rarely used,
yet each has some value. Two of which, unsafe and fixed, make sense. The
third, stackalloc, could probably have been written as an API function or
a function of new, each other keyword has a practical common usage.
I don't think drawing a line between null pointers and unintialized enums
is a particularly valid point, either.

Both are using "0" to mean something other than the integer 0. A bad idea.
 
Mike Schilling said:
Daniel O'Connell said:
Mike Schilling said:
message

Hi Philippe,

You are saying to users that for all but these two special
constants, the
language behaves on way but for these two magic constants you have
to
explicitly cast.

There is no magic. The language designers wanted to avoid
constructs like "(SomeEnum)0", because enums may be
[Flags]-able, and thus they may be "0" w/out having
a literal with the value "0".

Much like Dennis Ritchie wanted to avoid introducing a keyword for the
null pointer, when "0" worked so well :-) That was a horrifically bad
decision: look at any newsgroup for C or C++ beginners: wheneve the
subject arises it's clear that 90% of them don't understand that a
null pointer and the number zero are wholly different objects that
happen to be spelled the same. Likewise, a special constant
no_flags_set (or some such) that's a member of all flags-enabled enums
would be much clearer.

I disagree. Adding an implicit value removes one possible value from
all enums

That would be true if it were a qualified name (MyEnum.no_flags_set),
not if, as I suggested, it's a language keyword.

That is what I took you as meaning, yes.

Then please explain how it removes a value from all enums.

Simple, if every enum gains a member "NoFlag" or whatever, then you are no
longer able to provide a "NoFlag" enum member of your own.
Saying "it's silly" doesn't asnwer the question.


Two enum mebers that mean the same thing, one of which makes sense in the
context of the enum and the other is there because someone thinks that
implicit cast from zero is *a bad idea*. That is the definition of silly,
IMHO.

Both are using "0" to mean something other than the integer 0. A bad
idea.

Except that an enum is an integer, except it has cute names for some
members. Every enum *is* based off an integer(or byte, technically, which is
an integer for the purposes of this discussion). Not understanding that an
enum is infact an integer with some named values is going to cause huge
amounts of issue with people. The implicit 0 conversion isn't the least of
them, but it is certainly consistent with every other issue you'll run into.

That said, I would have probably not complained if the designers had chosen
to require explicit casting of the 0 constant into an enum, but I personally
think you can deal with that quirk.

The compiler warning could probably be better, and more to the point the
compiler should issue an explicit warning when there is both an enum and an
integer overload(with casts to distinguish and stop the warning), however I
don't think it requires a change to the language.
 
The point still is that 0 is normally an integer constant and this special
rule causes it to have a special meaning. To users, this is confusing
because:
- one integer constant (0) does not behave like another integer constant
(say 1)
- an integer variable with value 0 does not behave like the integer
constant.

Perhaps the rule 6.1.3 should only apply to enums marked as bitwise (flags)?

More interesting is why does one explicit cast, (double)0.0, disables the
implicit int promotion but another explicit cast, (int)0, does not disable
the implicit enum promotion?

Also, from the MSDN I pulled "The same value can be assigned to multiple
fields. When this occurs, you must mark one of the values as the primary
enumeration value for purposes of reflection and string conversion." So how
does one mark a value as the primary enumeration value in C#?

Philippe
 
Philippe Bertrand said:
The point still is that 0 is normally an integer constant and this special
rule causes it to have a special meaning. To users, this is confusing
because:
- one integer constant (0) does not behave like another integer constant
(say 1)
In most cases, atleast. In virtually all other situations this isn't an
issue.
- an integer variable with value 0 does not behave like the integer
constant.

It shouldn't, they are two different things. You also can't pass the
constant 0 by ref.
Perhaps the rule 6.1.3 should only apply to enums marked as bitwise
(flags)?

More interesting is why does one explicit cast, (double)0.0, disables the
implicit int promotion but another explicit cast, (int)0, does not disable
the implicit enum promotion?
Because (double)0.0 results in a double constant, (int)0 is still an int(I
think the compiler removes superflous casts like this).
Also, from the MSDN I pulled "The same value can be assigned to multiple
fields. When this occurs, you must mark one of the values as the primary
enumeration value for purposes of reflection and string conversion." So
how
does one mark a value as the primary enumeration value in C#?

On that I have *no* idea. I think the enum structure simply uses the first
defined, but I could be wrong on that.

That is something to look into...
 
Because (double)0.0 results in a double constant, (int)0 is still an int(I
think the compiler removes superflous casts like this).

0.0 is a double both before and after the cast. The fact that they act
differently is, to me, the strangest part.

Mike
 
Philippe Bertrand said:
The point still is that 0 is normally an integer constant and this special
rule causes it to have a special meaning. To users, this is confusing
because:
- one integer constant (0) does not behave like another integer constant
(say 1)
- an integer variable with value 0 does not behave like the integer
constant.

Precisely like null pointers in C:

(int *)0 yields a null pointer
int x = 0; (int *)x does not.

And a bad idea, for the same reason.
 
Back
Top