23

Consider the following code (it's a little long, but hopefully you can follow):

class A
{
}

class B : A
{
}

class C
{
    public virtual void Foo(B b)
    {
        Console.WriteLine("base.Foo(B)");
    }
}

class D: C
{
    public override void Foo(B b)
    {
        Console.WriteLine("Foo(B)");
    }

    public void Foo(A a)
    {
        Console.WriteLine("Foo(A)");
    }
}

class Program
{
    public static void Main()
    {
        B b = new B();
        D d = new D ();
        d.Foo(b);
    }
}

If you think the output of this program is "Foo(B)" then you'd be in the same boat as me: completely wrong! In fact, it outputs "Foo(A)"

If I remove the virtual method from the C class, then it works as expected: "Foo(B)" is the output.

Why does the compiler choose the version that takes a A when B is the more-derived class?

Dean Harding
  • 71,468
  • 13
  • 145
  • 180
  • Although I wont write such code, I find it a little surprising too :) Good question. – leppie Sep 09 '10 at 06:59
  • Hi. I added a constructor to both and put this in Foo(A a) a.GetType().Name and it says that it is type B. – Kieran Sep 09 '10 at 07:00
  • Could you please elaborate on the "abstract method from the C class" a bit? Did you remove it already? – Brian Rasmussen Sep 09 '10 at 07:00
  • Beats me. Interesting question. BTW, I make the assumption that that the word "abstract" was really meant to be "virtual"? If I remove that method from `C` and remove the corresponding `override` keyword from `D`, the output becomes `"Foo(B)"`. – Fredrik Mörk Sep 09 '10 at 07:04
  • @Brian, @Fredrik: sorry, I changed the code to simplify it a bit (virtual methods work just as well as abstract ones for this problem) and forgot to change the text underneath :) – Dean Harding Sep 09 '10 at 08:09
  • @leppie: I wouldn't want to write code like this either, but it's an inherited project :-) – Dean Harding Sep 09 '10 at 08:09
  • @leppie: you wouldn't write such code? Why not, what's wrong with it? Suppose Foo(A) performs some kind of conversion to construct a B out of an A and then it calls Foo(B). Ooops.... stack overflow! And if the user calls Foo(new B()) he's really calling Foo(A) so Foo(A) has to check "is this really a B?" ... what a mess, and no compiler warnings... – Qwertie Nov 29 '13 at 18:41

5 Answers5

15

The answer is in the C# specification section 7.3 and section 7.5.5.1

I broke down the steps used for choosing the method to invoke.

  • First, the set of all accessible members named N (N=Foo) declared in T (T=class D) and the base types of T (class C) is constructed. Declarations that include an override modifier are excluded from the set (D.Foo(B) is exclude)

    S = { C.Foo(B) ; D.Foo(A) }
    
  • The set of candidate methods for the method invocation is constructed. Starting with the set of methods associated with M, which were found by the previous member lookup, the set is reduced to those methods that are applicable with respect to the argument list AL (AL=B). The set reduction consists of applying the following rules to each method T.N in the set, where T (T=class D) is the type in which the method N (N=Foo) is declared:

    • If N is not applicable with respect to AL (Section 7.4.2.1), then N is removed from the set.

      • C.Foo(B) is applicable with respect to AL
      • D.Foo(A) is applicable with respect to AL

        S = { C.Foo(B) ; D.Foo(A) }
        
    • If N is applicable with respect to AL (Section 7.4.2.1), then all methods declared in a base type of T are removed from the set. C.Foo(B) is removed from the set

          S = { D.Foo(A) }
      

At the end the winner is D.Foo(A).


If the abstract method is removed from C

If the abstract method is removed from C, the initial set is S = { D.Foo(B) ; D.Foo(A) } and the overload resolution rule must be used to select the best function member in that set.

In this case the winner is D.Foo(B).

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Julien Hoarau
  • 48,964
  • 20
  • 128
  • 117
  • 3
    However, that does not remove the method found in the base type, which is an exact match given the argument list. It think it's rather the following text *"If N is applicable with respect to A (Section 7.4.2.1), then all methods declared in a base type of T are removed from the set."* (here: http://msdn.microsoft.com/en-us/library/aa691356(VS.71).aspx) that describes the reason for this behavior. Since the non-virtual method in `D` is a match, methods from the base types are removed. – Fredrik Mörk Sep 09 '10 at 07:15
  • @Fredrik, as per specs, overload resolution will not even happen here. Relevant sections are 7.5.5.1 (http://msdn.microsoft.com/en-us/library/aa691356(v=VS.71).aspx) that talks about method invocations. So candidate method set is created using section 7.3 and then overload resolution (7.4.2) may be applied to reduce set. In this case, lookup as per 7.3 will throw only one (non-virtual) method - no need to do overload resolution. – VinayC Sep 09 '10 at 07:15
  • yeah thats what i wanted to say but in am much more eloquent way. – Kieran Sep 09 '10 at 07:15
  • @VinayC: yes, that was sort of my point. Since there is only one matching method when the candidates have been identified by the compiler (given how I interpret the document that we both linked to), there is no overload resolution needed. According to the specs, the better-matching, but virtual method in the base class has been removed from the list in favor of the matching non-virtual method in the type at hand. – Fredrik Mörk Sep 09 '10 at 07:20
  • @Fredrik, I had re-read and got your point. What you are saying is that rules from 7.3 will actually throw up two methods but reduction rules in 7.5.5.1 will remove the base method. I was under impression that 7.3 will not throw up two methods but I was actually wrong. – VinayC Sep 09 '10 at 07:26
  • Ah yes, that makes sense now... it's the `override` that causes the Foo() override in `D` to be removed. – Dean Harding Sep 09 '10 at 08:15
  • Yuck! I'm in this situation right now and thinking about putting a `public abstract void Foo(A a)` in the base class, and `sealed override void Foo(A a)` in the derived class, just to fix the overload resolution. But what if I weren't allowed to change the base class, then what would I do? I could add a dynamic check in `Foo(A a)` to test if `A` is actually a `B`, but that's inefficient (and note that if it IS a `B`, I cannot pass it to `Foo(B)`! Stack Overflow!). I could rename `Foo(A a)` to `FooWithA(A a)` but it's kind of a kludge... – Qwertie Nov 29 '13 at 18:55
10

Why does the compiler choose the version that takes a A when B is the more-derived class?

As others have noted, the compiler does so because that's what the language specification says to do.

This might be an unsatisfying answer. A natural follow-up would be "what design principles underly the decision to specify the language that way?"

That is a frequently asked question, both on StackOverflow and in my mailbox. The brief answer is "this design mitigates the Brittle Base Class family of bugs."

For a description of the feature and why it is designed the way it is, see my article on the subject:

http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

For more articles on the subject of how various languages deal with the Brittle Base Class problem see my archive of articles on the subject:

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

Here's my answer to the same question from last week, which looks remarkably like this one.

Why are signatures declared in the base class ignored?

And here are three more relevant or duplicated questions:

C# overloading resolution?

Method overloads resolution and Jon Skeet's Brain Teasers

Why does this work? Method overloading + method overriding + polymorphism

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • This is a horrible rule. If you change `override` to `new` in the derived class, the compiler behaves as everyone expects it to. I don't see how changing the behavior for `public override` vs `public new` has anything to do with the brittle base class problem. Since it's an override, the derived class author is clearly aware of the presence of the base class method. Is there anyone who is not baffled and annoyed by this? (If you are not baffled, huh? why not?) – Qwertie Nov 29 '13 at 18:34
  • @qwertie if you do not understand why this mitigates the BBC problem then read about it and think about it until you do would be my advice. – Eric Lippert Dec 02 '13 at 07:08
  • 1
    @Qwertie: Moreover: any knowledge possessed by *the author* of the derived class is irrelevant to this question. It is the *customer* of the author of the derived class who is the relevant party; why should that person have to know whether a method is overridden in a particular class as opposed to its base class? **That's an implementation detail subject to change**, not a part of the contract of the class! – Eric Lippert Dec 02 '13 at 14:55
  • WTF? The caller ("customer") has the *same* knowledge as the author. He knows he is calling class D. The caller goes to a D declaration, presses F12, and sees `Foo(A a)` and `Foo(B a)`. The caller has no more reason to expect `Foo(new B())` will invoke `Foo(A a)` than the class's author did! Nor does the caller have more reason to expect `override` to behave different than `new` than the original author did. – Qwertie Dec 05 '13 at 22:16
  • I kinda see what you're saying, but there are multiple ways this rule can shoot you in the foot. Have you considered this scenario: the author of D knows, or notices, that derived class methods "take precedence" over base class methods, so he realizes that defining Foo(A a) blocks access to the base class method Foo(B b). So he writes "override Foo(B b) {base.Foo(b);}" to avoid that unwanted behavior. Oops--it didn't work. Now what? I'd be surprised if you can find any scenario where this behavior is good. Nor is it intuitive (or this question would not have been asked). – Qwertie Dec 05 '13 at 22:24
  • @Qwertie: The customer does not have the same knowledge as the author; the author knows what class contains the override *and why*. Moreover, the author has the choice of overriding in the more derived class or implementing a *third* class that sits between the base and derived classes which does the override. Now: should overload resolution choose a *different* method when *an override* -- which is an implementation detail -- is moved towards the larger class in the hierarchy? That would be very strange indeed! – Eric Lippert Dec 05 '13 at 23:41
  • @Qwertie: To answer your question: there are many scenarios where this behavior is desirable. They are (1) when the author of the derived class *knows more* than the author of the base class. This is almost always the case! The author of the derived class knows better than the author of the base class what the semantics of each override should be for the derived class. – Eric Lippert Dec 05 '13 at 23:44
  • @Qwertie: And (2) the brittle base class scenarios, whereby the author of the base class adds a method *after* the author of the derived class has written the derived class. It is very strange that *that* author, who knows *less* than the author of the derived class, should be put in the position of deciding the behavior of the derived class *when used by the customer consuming the derived class*. That author has the least knowledge but you are putting them in charge of the decision! C#'s design mitigates this class of failures. – Eric Lippert Dec 05 '13 at 23:45
  • @Qwertie: Take a step back here. The behavior that you decry is the consequence of two rules. (1) Methods in derived classes were written by someone who knew more about the desired behavior than the author of the base class, therefore they take precedence over methods of the base class. That is, when faced with a choice, the compiler should default to preferring the method of the more derived class. (2) Choosing where in a hierarchy to override a virtual method is an invisible implementation detail, subject to change, and not part of the public surface of a class. – Eric Lippert Dec 06 '13 at 00:29
  • @Qwertie: Now, it is perfectly reasonable to say "those rules are reasonable, but the consequences of those rules working together produce a surprising conclusion, therefore we should abandon both rules". **All designs are the result of a series of compromises between conflicting principles**. The C# design team believes that both of those rules are sensible and moreover, that they mitigate an important class of failures seen in practice in other languages, and therefore decided that the benefits exceeded the costs. That does not make it a "horrible" rule; it makes it a reasonable rule. – Eric Lippert Dec 06 '13 at 00:32
2

I think it is because in case of a non-virtual method the compile time type of the variable on which the method is invoked is used.

You have the Foo method which is non-virtual and hence that method is called.

This link has very good explanation http://msdn.microsoft.com/en-us/library/aa645767%28VS.71%29.aspx

Unmesh Kondolikar
  • 9,256
  • 4
  • 38
  • 51
2

So, here is how it should work according to the specification (at compile time, and given that I navigated the documents correctly):

The compiler identifies a list of matching methods from the type D and its base types, based on the method name and the argument list. This means that any method named Foo, taking one parameter of a type to which there is an implicit conversion from B are valid candidates. That would produce the following list:

C.Foo(B) (public virtual)
D.Foo(B) (public override)
D.Foo(A) (public)

From this list, any declarations that include an override modifier are excluded. That means that the list now contains the following methods:

C.Foo(B) (public virtual)
D.Foo(A) (public)

At this point we have the list of matching candidates, and the compiler is now to decide what to call. In the document 7.5.5.1 Method invocations, we find the following text:

If N is applicable with respect to A (Section 7.4.2.1), then all methods declared in a base type of T are removed from the set.

This essentially means that if there is an applicable method declared in D, any methods from base classes will be removed from the list. At this point we have a winner:

D.Foo(A) (public)
Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
  • Yes, this clears things up quite a bit... now I just got to promise myself that I'll never write code like this :-) – Dean Harding Sep 09 '10 at 08:24
0

I think that when implementing another class it looks as far up the tree to get an solid implementation of a method. As there is no method being called it is using the base class.

public void Foo(A a){
    Console.WriteLine("Foo(A)" + a.GetType().Name);
    Console.WriteLine("Foo(A)" +a.GetType().BaseType );
}

thats a guess i am no pro at .Net

Kieran
  • 17,572
  • 7
  • 45
  • 53