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?
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 ALD.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, 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)
.