What's Wrong With This Code (A bug perhaps!)

G

Guest

Hopefully someone can set me straight on this issue.

I was testing some code where I have a method that is being overriden. The
method overrides were working fine until I passed a literal 0 (int value) in
as the lone argument.

Well there are two possible overrides that could be called with a single
argument. The first one takes an object and the second one takes an enum
type (SqlTypeCode see the code below). Since a literal 0 is not a
SqlTypeCode I would have expected the override taking an object argument to
be run. However what I found was the override taking the SqlTypeCode was run
instead.

It appears that whenever a literal value that qualifies as a valid enum
value cast to int is used c# will choose to use the SqlTypeCode override
instead of the object. Now whenever a variable of type int is used with a
value that can be cast to the SqlTypeCode value the object override
(expected) is used.

What gives? Is this a bug or am I just misunderstanding something?

If this is not a bug it seems this issue could be something that hides in
the bushes for a long time until it jumps out and bites you.

Thank you,
--
Gregg Walker

Here's the code from a console app...

using System;
using System.Collections.Generic;
using System.Text;

namespace EnumOverrideIssue
{
class Program
{
enum SqlTypeCode
{
Boolean,
Byte,
Char,
DateTime,
Decimal,
Int16,
Int32,
Int64,
Single,
String,
TimeSpan
}

static object myValue = 0;
static SqlTypeCode mySqlType = SqlTypeCode.Int32;

static void Main(string[] args)
{
mySqlType = SqlTypeCode.Int32; string sqlString = ToSqlString();
Console.WriteLine("SqlString=" + sqlString);
// Pass in 0 as literal int
mySqlType = SqlTypeCode.Int32; sqlString = ToSqlString(0);
Console.WriteLine("SqlString=" + sqlString); // Returns 'true' instead of '0'
mySqlType = SqlTypeCode.Int32; sqlString = ToSqlString((int)0);
Console.WriteLine("SqlString=" + sqlString); // Returns 'true' instead of '0'
// Pass in 0 as variable int
myValue = 0; mySqlType = SqlTypeCode.Int32; sqlString =
ToSqlString(myValue); Console.WriteLine("SqlString=" + sqlString); //
Correctly returns '0'
mySqlType = SqlTypeCode.DateTime; sqlString =
ToSqlString(DateTime.Today); Console.WriteLine("SqlString=" + sqlString);

Console.ReadLine();
}

static string ToSqlString()
{
return ToSqlString(myValue, mySqlType);
}

static string ToSqlString(object dataValue)
{
return ToSqlString(dataValue, mySqlType);
}

static string ToSqlString(SqlTypeCode sqlTypeCode)
{
return ToSqlString(myValue, sqlTypeCode);
}

static string ToSqlString(object dataValue, SqlTypeCode sqlTypeCode)
{
switch(sqlTypeCode)
{
case SqlTypeCode.Boolean: return Convert.ToBoolean(dataValue).ToString();
case SqlTypeCode.Byte: return Convert.ToByte(dataValue).ToString();
case SqlTypeCode.Char: return "'" + Convert.ToChar(dataValue).ToString()
+ "'";
case SqlTypeCode.DateTime: return
Convert.ToDateTime(dataValue).ToString();
case SqlTypeCode.Decimal: return Convert.ToDecimal(dataValue).ToString();
case SqlTypeCode.Int16: return Convert.ToInt16(dataValue).ToString();
case SqlTypeCode.Int32: return Convert.ToInt32(dataValue).ToString();
case SqlTypeCode.Int64: return Convert.ToInt64(dataValue).ToString();
case SqlTypeCode.Single: return Convert.ToSingle(dataValue).ToString();
case SqlTypeCode.String: return "'" +
Convert.ToString(dataValue).ToString() + "'";
case SqlTypeCode.TimeSpan: return
TimeSpan.Parse(dataValue.ToString()).ToString();
}

return "NULL";
}
}
}
 
G

Gregg Walker

BTW... I can force this to work the way I'm expecting by providing the
following as a third single argument override.

static string ToSqlString(int dataValue)
{
return ToSqlString(dataValue, mySqlType);
}

This indirectly forces the object override to be called instead of the
SqlTypeCode override in the case of literal ints that have a valid value in
the enum definition.
 
P

Peter Duniho

[...]
It appears that whenever a literal value that qualifies as a valid enum
value cast to int is used c# will choose to use the SqlTypeCode override
instead of the object. Now whenever a variable of type int is used with
a value that can be cast to the SqlTypeCode value the object override
(expected) is used.

What gives? Is this a bug or am I just misunderstanding something?

I'm too new to C# to be able to explain why it casts literals, but boxes
variables. On the face of it, I agree that appears to be inconsistent.
I'll bet there's some reasonably good reason though.

However, it does seem to me that you don't really need a third overload to
fix the issue. If you'd just cast the parameter explicitly (to "object"),
then that would ensure that you use the overload you expect. I think it's
a bad idea to have ambiguous calls to overloaded methods anyway. Even if
the compiler rules are explicit, people reading the code should be able to
easily see what overload actually gets called.

I'm also not clear on why the "doesn't work" case returns "true" instead
of "false", but I'm probably just missing something in the code you posted.

Pete
 
J

Jon Skeet [C# MVP]

Gregg Walker said:
I was testing some code where I have a method that is being overriden. The
method overrides were working fine until I passed a literal 0 (int value) in
as the lone argument.

Well there are two possible overrides that could be called with a single
argument. The first one takes an object and the second one takes an enum
type (SqlTypeCode see the code below). Since a literal 0 is not a
SqlTypeCode I would have expected the override taking an object argument to
be run. However what I found was the override taking the SqlTypeCode was run
instead.

There's an implicit conversion from 0 to any enum type.
It appears that whenever a literal value that qualifies as a valid enum
value cast to int is used c# will choose to use the SqlTypeCode override
instead of the object.

Well, according to the C# spec, only 0 should be converted without
warning. I believe that both the Mono and MS compilers also convert any
hex literal to be converted.

I'm not exactly sure why the conversion is there at all. It's useful
sometimes as an easy way of getting an enum value, but I agree it's a
potential source of problems.
 
J

Jon Skeet [C# MVP]

On May 18, 2:00 am, "Peter Duniho" <[email protected]>
wrote:

I think it's
a bad idea to have ambiguous calls to overloaded methods anyway. Even if
the compiler rules are explicit, people reading the code should be able to
easily see what overload actually gets called.

Just for your entertainment Pete, have a look at this program, try to
work out what it will print, and then run it. (There's no bug in the
compiler, btw. The spec really is written this way.)

using System;

class Base
{
public virtual void Foo(int x)
{
Console.WriteLine("Base.Foo(int)");
}
}

class Derived : Base
{
public override void Foo(int x)
{
Console.WriteLine("Derived.Foo(int)");
}

public void Foo(object o)
{
Console.WriteLine("Derived.Foo(object)");
}
}

class Test
{
static void Main()
{
new Derived().Foo(1);
}
}

Jon
 
J

Jon Skeet [C# MVP]

is there any reasonable explanation for this behaviour?

There's a reasonable reason for the behaviour if the override wasn't
present - it eliminates some versioning issues (i.e. adding the
Foo(object) in Basewouldn't change what any previous code called).

However, in the case where there's an override in Derived, it's more
reasonable to call Foo(int). After all, that involves Derived
changing, not just Base. I think this case was basically just missed
as an oversight.

Jon
 
L

Linda Liu [MSFT]

Hi Gregg,

I performed a test based on your sample code and did see the problem.

I agree with Jon that only 0 can be implicitly converted to any enum type.
My test shows that literal values other than 0 couldn't be implicitly
converted to an enum type.

If you pass a literal 1 to the ToSqlString method, you will find that the
ToSqlString(object) is called actually. It means that literal 1 is not
implicitly converted to the SqlTypeCode enum type.

To summarize, when a method has several overloads, the most matched
overload will be called. If you pass a literal value to the method, CLR
tries to implicitly convert it and then select the most matched overload.

As for the literal 0, CLR tries to implicitly convert it to the type of
Int32 first. If there is a overload that takes an Int32 parameter, this
overload will be called. If not, the literal 0 is implicitly converted to
any enum type. If there is a overload taking an enum parameter, the
overload will be called. If neither of the two overloads exists, the
literal 0 is implicitly converted to the type of object.

At last, I suggest that you explicitly convert the literal 0 to the type
you want, so as to get the expected overload to be called correctly.

Hope this helps.
If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
J

Jon Skeet [C# MVP]

On May 18, 10:50 am, (e-mail address removed) (Linda Liu [MSFT])
wrote:

As for the literal 0, CLR tries to implicitly convert it to the type of
Int32 first. If there is a overload that takes an Int32 parameter, this
overload will be called. If not, the literal 0 is implicitly converted to
any enum type. If there is a overload taking an enum parameter, the
overload will be called. If neither of the two overloads exists, the
literal 0 is implicitly converted to the type of object.

Just to clarify, I don't believe the CLR has anything to do with the
choice of overload - that's done by the C# compiler, and the signature
to use is specified in the IL (i.e. the CLR doesn't have to make a
decision).

Jon
 
G

Gregg Walker

Thanks to all who responded. I feel enlightened though a bit shocked that
this is indeed the expected behavior (according to specs).
There's an implicit conversion from 0 to any enum type.

Thank you for that clarification.
Well, according to the C# spec, only 0 should be converted without
warning. I believe that both the Mono and MS compilers also convert any
hex literal to be converted.

I'm not exactly sure why the conversion is there at all. It's useful
sometimes as an easy way of getting an enum value, but I agree it's a
potential source of problems.

Now that's the strangest spec I can think of. Doesn't seem to be any good
reason for it on the surface and yes a potential source of problems.

IMHO this is hole that needs to be plugged. In a language that does not
allow the following...

int x = 0;

if(x) return;

.... or

if(0) return;

....it doesn't make sense to allow an implicit conversion from a literal 0 to
enum. It breaks away from the type safety afforded by the language.

The spec gods need to revisit this one.

Once again thanks for all your responses.
 
G

Gregg Walker

Jon - I tried looking at the available compiler options to see if there's a
switch to disable this behavior and I could not find one. Are you aware of
any switches that might affect this behavior?
 
J

Jon Skeet [C# MVP]

Gregg Walker said:
Jon - I tried looking at the available compiler options to see if there's a
switch to disable this behavior and I could not find one. Are you aware of
any switches that might affect this behavior?

No - fortunately, IMO. I'm not a big fan of compiler switches beyond
"make warnings errors" or "allow unsafe code".

I *think* that the C# spec allows compilers to add extra warnings of
their own, and this would be a very good case to take advantage of
that. (Then turning warnings to errors would stop the build from
working.) Admittedly it wouldn't just make the overload do the right
thing, but it would make it more obvious.
 
G

Gregg Walker

I *think* that the C# spec allows compilers to add extra warnings of
their own, and this would be a very good case to take advantage of
that.

I'll look into doing just that.

Thanks for your help.
 
J

Jon Skeet [C# MVP]

Gregg Walker said:
I'll look into doing just that.

Do you mean in terms of making a request to Microsoft? If you do and
you need some more votes, I'd be happy to vote for such a feature :)
 
L

Linda Liu [MSFT]

Hi Jon,

Thank you for pointing out my mistake.

Yes, it's the C# compiler that determine which oveload is to be called.


Sincerely,
Linda Liu
Microsoft Online Community Support
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top