开发者

Overload resolution and virtual methods

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

class A
{
}

class B :开发者_JAVA百科 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?


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


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


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


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)


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜