Confused by overloading (or can I claim it's a compiler bug...?)

A

AJ

Why does the following behave as it does...?

public class Base
{
public void Add( byte b )
{
}
}

public class TestClass : Base
{
public void Add( int s )
{
byte x = 0;
Add( x );
}
}

then call with
new TestClass().Add( 10 );

-> The Add(int) override gets called (good). Which in turn class the
___Add(int)___ override.

Why doesn't this second call hit the base class's Add( byte ) ??

[It works as I'd expected if the Add(byte) method is defined in
TestClass rather than Base, or you call base.Add(x) ]
 
A

Andy

Why does the following behave as it does...?

public class Base
{
public void Add( byte b )
{
}
}

public class TestClass : Base
{
public void Add( int s )
{
byte x = 0;
Add( x );
}
}

then call with
new TestClass().Add( 10 );

-> The Add(int) override gets called (good). Which in turn class the
___Add(int)___ override.

Why doesn't this second call hit the base class's Add( byte ) ??

[It works as I'd expected if the Add(byte) method is defined in
TestClass rather than Base, or you call base.Add(x) ]

Look at your compiler errors; you should be getting a warning that
TestClass is hiding a member of the base class. You need to add the
virtual keyword to the method definition in the base class, then add
the overrides keyword to the method definition in the subclass. If
you need to call the base, you need to do so yourself.
 
N

Nicholas Paldino [.NET/C# MVP]

That's not correct. There is no hiding here.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Andy said:
Why does the following behave as it does...?

public class Base
{
public void Add( byte b )
{
}
}

public class TestClass : Base
{
public void Add( int s )
{
byte x = 0;
Add( x );
}
}

then call with
new TestClass().Add( 10 );

-> The Add(int) override gets called (good). Which in turn class the
___Add(int)___ override.

Why doesn't this second call hit the base class's Add( byte ) ??

[It works as I'd expected if the Add(byte) method is defined in
TestClass rather than Base, or you call base.Add(x) ]

Look at your compiler errors; you should be getting a warning that
TestClass is hiding a member of the base class. You need to add the
virtual keyword to the method definition in the base class, then add
the overrides keyword to the method definition in the subclass. If
you need to call the base, you need to do so yourself.
 
A

AJ

Why does the following behave as it does...?

public class Base
{
public void Add( byte b )
{
}
}

public class TestClass : Base
{
public void Add( int s )
{
byte x = 0;
Add( x );
}
}

then call with
new TestClass().Add( 10 );

-> The Add(int) override gets called (good). Which in turn class the
___Add(int)___ override.

Why doesn't this second call hit the base class's Add( byte ) ??

[It works as I'd expected if the Add(byte) method is defined in
TestClass rather than Base, or you call base.Add(x) ]

Look at your compiler errors; you should be getting a warning that
TestClass is hiding a member of the base class. You need to add the
virtual keyword to the method definition in the base class, then add
the overrides keyword to the method definition in the subclass. If
you need to call the base, you need to do so yourself.

I'm overloading, not overriding..
 
J

Jon Skeet [C# MVP]

-> The Add(int) override gets called (good). Which in turn class the
___Add(int)___ override.

Why doesn't this second call hit the base class's Add( byte ) ??

<snip>

Because the spec is very, very weird when it comes to overloading.
From memory, when it tries to work out which overload to take, if
there are *any* valid signatures (i.e. ones which can match with
implicit conversion) in a more derived class than a less derived
class, all signatures in the less derived class are effectively
removed.

What's even weirder is that this includes signatures which are
overridden in the derived class:
using System;

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

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

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

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

The output, contrary to intuitive expectations, is
"Derived.Foo(object)".

It's bonkers if you ask me, but that's what the spec says...

Jon
 
A

AJ

<snip>

Because the spec is very, very weird when it comes to overloading.
there are *any* valid signatures (i.e. ones which can match with
implicit conversion) in a more derived class than a less derived
class, all signatures in the less derived class are effectively
removed.

What's even weirder is that this includes signatures which are
overridden in the derived class:

That's nasty..... So, if my original example started as a single class,
then I refactor to create the base class (without changing anything
else), I suddenly get different behaviour (because the Add(byte) is no
longer being called.)

Ugh. Ugh. Ugh.
 
J

Jon Skeet [C# MVP]

That's nasty..... So, if my original example started as a single class,
then I refactor to create the base class (without changing anything
else), I suddenly get different behaviour (because the Add(byte) is no
longer being called.)

Ugh. Ugh. Ugh.

Absolutely. I believe this to be unintentional. When I contacted a
couple of members of the ECMA committee, they were somewhat horrified
too :)

Jon
 
V

VJ

This really explains a problem we had in the last 2 days, and I was finally
going to post. This helps..! Thanks Jon.. AJ...

VJ
 
A

AJ

Absolutely. I believe this to be unintentional. When I contacted a
couple of members of the ECMA committee, they were somewhat horrified
too :)

Do you mean an unintentional sideeffect of the rules, or an unitentional
'feature' in the compiler?

I've just briefly downloaded and looked at "ECMA-334 C# Language
specification 4th Edition/ June 2006"

I can see rules about deciding which overloaded function to use (sect.
14.4.2) , but can't see any mention that inheritance comes into the
decision..
 
J

Jon Skeet [C# MVP]

AJ said:
Do you mean an unintentional sideeffect of the rules, or an unitentional
'feature' in the compiler?

Unintentional side-effect of the rules. The compiler obeys the spec
here.
I've just briefly downloaded and looked at "ECMA-334 C# Language
specification 4th Edition/ June 2006"

I can see rules about deciding which overloaded function to use (sect.
14.4.2) , but can't see any mention that inheritance comes into the
decision..

It's in 14.5.5.1:

<quote>
The set of candidate methods is reduced to contain only methods from
the most derived types: For each method C.F in the set, where C is the
type in which the method F is declared, all methods declared in a
base type of C are removed from the set.
</quote>

(An overriding method doesn't count as a declaration - the declaration
is in the base class.)

It's not the easiest spec in the world to navigate, unfortunately.
 
B

Barry Kelly

Jon said:
Because the spec is very, very weird when it comes to overloading.

It's actually with good reason: it makes it safe to add new overloads to
base classes in the framework without worrying that existing programs
are going to break when recompiled, due to finding the new overload when
the new framework is released.
It's bonkers if you ask me, but that's what the spec says...

AndersH actually appears to be very proud of this feature of C#, I
recall watching a video where he pointed out that he wasn't aware of any
other language that did overloading this way.

-- Barry
 
B

Barry Kelly

Jon said:
The output, contrary to intuitive expectations, is
"Derived.Foo(object)".

It's bonkers if you ask me, but that's what the spec says...

Whups - I do agree with you about the overridden version though, it
shouldn't remove overridden overloads, as that's evidence that the
descendant is aware of the ancestor's method.

-- Barry
 
J

Jon Skeet [C# MVP]

Barry Kelly said:
It's actually with good reason: it makes it safe to add new overloads to
base classes in the framework without worrying that existing programs
are going to break when recompiled, due to finding the new overload when
the new framework is released.

That's a reasonable point - but I still find it counterintuitive, and
it seems that others do too.
AndersH actually appears to be very proud of this feature of C#, I
recall watching a video where he pointed out that he wasn't aware of any
other language that did overloading this way.

I don't see that as an advantage - I see it as a positive disadvantage.
It means that everyone has to read the specification in considerable
detail in order to be able to predict what their code will do.

Not only have I never found anyone who would intuitively expect the
current behaviour - I haven't found anyone who worked out why it was on
their first trawl through the spec, either. It always takes a very
careful analysis to find out what should actually happen. (It took me
ages first time.)
 
C

Chris Nahr

I don't see that as an advantage - I see it as a positive disadvantage.
It means that everyone has to read the specification in considerable
detail in order to be able to predict what their code will do.

I would agree, but framework versioning is apparently the highest
priority for the CLR team. I once submitted a similar request to
change the overload resolution of generic methods (non-generic base
class methods are ignored if a matching generic derived method is
found), and I got the same reply:

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=92075

"The reason we 'stop the search' in the current type is because of
versioning. If we didn't, then it would be possible for someone to add
a method in the base class and suddenly the code that was behaving one
way, will start to do something different because the new method will
be called. This is a variant of the fragile base class problem."
 
A

AJ

That's a reasonable point - but I still find it counterintuitive, and
it seems that others do too.

I'm still not convinced that it's the 'right' decision, nor that the
spec really explains it that well (if at all..)

However, if you're all suggesting that the behaviour is correct, then
the IDE has a bug - the intellisense displays the two overloads as
options where, in my example, I don't think that the Add(byte) method is
_ever_ going to get called, since the byte will always get promoted to
an int..
 
B

Ben Voigt

Barry Kelly said:
Whups - I do agree with you about the overridden version though, it
shouldn't remove overridden overloads, as that's evidence that the
descendant is aware of the ancestor's method.

And as nearly as I can tell, that's how C++ works -- you can't overload a
base member function, you end up overriding, but your own overrides of base
functions are also considered.
 

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