views:

147

answers:

5

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?

+1  A: 

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
+8  A: 

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

madgnome
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
@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
yeah thats what i wanted to say but in am much more eloquent way.
Kieran
You're right, I'll fix that.
madgnome
@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
@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
Ah yes, that makes sense now... it's the `override` that causes the Foo() override in `D` to be removed.
Dean Harding
A: 

Hello.

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
+1  A: 

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
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
+3  A: 

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.

http://stackoverflow.com/questions/3648070/why-are-the-signatures-declared-in-base-class-are-ignored/3648227#3648227

And here are three more relevant or duplicated questions:

http://stackoverflow.com/questions/2933674/c-overloading-resolution/2933724

http://stackoverflow.com/questions/2744528/method-overloads-resolution-and-jon-skeets-brain-teasers

http://stackoverflow.com/questions/1833216/why-does-this-work-method-overloading-method-overriding-polymorphism

Eric Lippert