13

So I was playing with C# to see if it matched C++ behavior from this post: http://herbsutter.com/2013/05/22/gotw-5-solution-overriding-virtual-functions/ when I came across this very strange behavior:

public class BaseClass
{
    public virtual void Foo(int i)
    {
        Console.WriteLine("Called Foo(int): " + i);
    }

    public void Foo(string i)
    {
        Console.WriteLine("Called Foo(string): " + i);
    }
}

public class DerivedClass : BaseClass
{
    public void Foo(double i)
    {
        Console.WriteLine("Called Foo(double): " + i);
    }
}

public class OverriddenDerivedClass : BaseClass
{
    public override void Foo(int i)
    {
        base.Foo(i);
    }

    public void Foo(double i)
    {
        Console.WriteLine("Called Foo(double): " + i);
    }
}

class Program
{
    static void Main(string[] args)
    {
        DerivedClass derived = new DerivedClass();
        OverriddenDerivedClass overridedDerived = new OverriddenDerivedClass();

        int i = 1;
        double d = 2.0;
        string s = "hi";

        derived.Foo(i);
        derived.Foo(d);
        derived.Foo(s);

        overridedDerived.Foo(i);
        overridedDerived.Foo(d);
        overridedDerived.Foo(s);
    }
}

Output

Called Foo(double): 1
Called Foo(double): 2
Called Foo(string): hi
Called Foo(double): 1
Called Foo(double): 2
Called Foo(string): hi

So apparently it favors the implicitly converted int to double over the more specific Foo(int) from the base class. Or does it hide the Foo(int) from the base class? But then: why isn't Foo(string) hidden? Feels very inconsistent... It also does not matter if I override Foo(int) or not; the outcome is the same. Can anyone explain what's going on here?

(Yes I know that it is bad practice to overload base methods in a derived class - Liskov and all - but I still wouldn't expect that Foo(int) in OverriddenDerivedClass isn't called?!)

Nebula
  • 1,045
  • 2
  • 11
  • 24
  • 4
    It's there, scroll down – ose May 23 '13 at 08:31
  • 3
    "Yes I know that it is bad practice to overload base methods in a derived class - Liskov and all" Huh, what? When are you allowed to override base methods then? Anyway +1 because I would expect the same, curious for the answer. – bas May 23 '13 at 08:33
  • Surely you meant to call the methods from `overrideDerived` in the second batch, didn't you? – Rotem May 23 '13 at 08:39
  • @bas: Over *load* he wrote, not over *ride*. (sorry for the spacing, seems like SO's Markdown cannot make parts of words italic) – O. R. Mapper May 23 '13 at 08:40
  • @Rotem Yes I did, good catch. The output doesn't change however... I edited the question. – Nebula May 23 '13 at 08:42
  • @O.R.Mapper (and Nebula): Ah whoops! Sorry! – bas May 23 '13 at 08:43
  • All I can think of is that the compiler tries to find a method that can handle the input in the class itself first and only if it doesn't, it goes to the base class and tries to find one there. And since `int` completely fits into `double`, that method get's called. (And because of cases like this, one should not over*load* base methods) – Corak May 23 '13 at 08:47
  • It's been a while since I've seen a good (interesting) question :) Now if we could summon Skeet that would be awesome :) – Dimitar Dimitrov May 23 '13 at 08:52
  • 1
    @DimitarDimitrov Prayeth to the Skeet, so he may come bless us in the holy light of wisdom. – Nolonar May 23 '13 at 08:53
  • 2
    Speaking of the great Skeet: http://stackoverflow.com/questions/2744528/method-overloads-resolution-and-jon-skeets-brain-teasers – Corak May 23 '13 at 08:53
  • @Corak I lol'd because it seems to be that it sorta answers the question ... That's so meta ! :) – Dimitar Dimitrov May 23 '13 at 08:55
  • 2
    Can't go wrong with Eric Lippert responding to a Jon Skeet brainteaser. – Corak May 23 '13 at 08:59
  • As quoted by Eric Lippert, The relevant part in the c# language spec is "7.5.3 Overload resolution": **methods in a base class are not candidates if any method in a derived class is applicable**. So, since there is an applicable method in DerivedClass, BaseClass is not even searched even though it would contain a seemingly better match – Paolo Falabella May 23 '13 at 09:41

1 Answers1

10

To explain how it works for the OverriddenDerivedClass example:

Have a look at the C# spec for member lookup here: http://msdn.microsoft.com/en-us/library/aa691331%28VS.71%29.aspx

That defines how the lookup is done.

In particular, look at this part:

First, the set of all accessible (Section 3.5) members named N declared in T and the base types (Section 7.3.1) of T is constructed. Declarations that include an override modifier are excluded from the set.

In your case, N is Foo(). Because of Declarations that include an override modifier are excluded from the set then the override Foo(int i) is excluded from the set.

Therefore, only the non-overridden Foo(double i) remains, and thus it is the one that is called.

That is how it works for the OverriddenDerivedClass example, but this is not an explanation for the DerivedClass example.

To explain that, look at this part of the spec:

Next, members that are hidden by other members are removed from the set.

The Foo(double i) in DerivedClass is hiding the Foo(int i) from the base class, so it is removed from the set.

The tricky thing here is the part that says:

All methods with the same signature as M declared in a base type of S are removed from the set.

You might say "But wait! Foo(double i) doesn't have the same signature as Foo(int i), so it shouldn't be removed from the set!".

However, because there is an implicit conversion from int to double, it is considered to have the same signature, so Foo(int i) is removed from the set.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • `((BaseClass)derived).Foo(i)` – Corak May 23 '13 at 08:50
  • Ok, thanks for that. But indeed: "...and t he base types (Section 7.3.1) of T... implies that BaseClass::Foo(int) would be within scope. What I can imagine is that the set looks like : DerivedClass::Foo(double), BaseClass::Foo(int). When it then takes the integer argument it knows it can implicitly convert an integer into a double and BaseClass::Foo(int) is not evaluated. This would only be true however when a normal class with Foo(double) Foo(int) has a set like this: NormalClass:Foo(int), NormalClass:Foo(double). Could we verify this? – Nebula May 23 '13 at 09:00
  • @Nebula Sorry, not too sure what you mean... However, the base `Foo(int)` is in scope but because it has the same signature as the derived class `Foo(double)` (because there is an implicit conversion from int to double) the derived class's `Foo(double)` causes the base class's `Foo(int)` to be removed from the set. – Matthew Watson May 23 '13 at 09:06
  • @MatthewWatson Yes, that is indeed what seems to happen. What is not clear to me however is how this decision is made: are derived methods favored over base class methods? Even if the type has to be converted (i.e. 'forced') to fit the signature? In other words, is it a decision made by the compiler (look at derived and base and compare sigatures) or does the compiler first look at the derived list first to see if it can make a match (any match at all, using implicit conversions and whatnot) and, if so, then disregards the base class altogether? – Nebula May 23 '13 at 09:15
  • @Nebula It follows exactly the steps outlined by the spec (see the link I posted for the full spec). The only important thing which isn't defined by that part of the spec is the crucial point that methods from the base class that have parameter types which have implicit conversions from the corresponding parameter types in the derived method are considered to have the same signature. I can't find that in the spec... But Eric Lippert's blog that someone linked explains it better: http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx – Matthew Watson May 23 '13 at 09:32
  • @MatthewWatson Thank you sir! Very fine answer indeed. Again a little more knowledge gained in the wordeful world of C# :-) – Nebula May 23 '13 at 11:35