25

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?

7
  • Although I wont write such code, I find it a little surprising too :) Good question. Commented Sep 9, 2010 at 6: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. Commented Sep 9, 2010 at 7:00
  • Could you please elaborate on the "abstract method from the C class" a bit? Did you remove it already? Commented Sep 9, 2010 at 7: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)". Commented Sep 9, 2010 at 7: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 :) Commented Sep 9, 2010 at 8:09

5 Answers 5

17

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).

Sign up to request clarification or add additional context in comments.

7 Comments

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: 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, as per specs, overload resolution will not even happen here. Relevant sections are 7.5.5.1 (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.
yeah thats what i wanted to say but in am much more eloquent way.
@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, 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.
|
12

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:

https://learn.microsoft.com/en-us/archive/blogs/ericlippert/future-breaking-changes-part-three

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

12 Comments

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 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.
@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!
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.
@TanveerBadar: No one in their right mind would ship a broken parser than runs in the kernel ring at machine startup and then also push an untested configuration file with syntax errors to a billion machines. Being robust in the face of companies doing stuff that no one in their right mind would do is a hard problem.
|
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

Comments

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)

1 Comment

Yes, this clears things up quite a bit... now I just got to promise myself that I'll never write code like this :-)
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.