As other answers noted, this is by design.
Consider a less complex example:
class Animal { public virtual void Eat(Apple a) { ... } } class Giraffe : Animal { public void Eat(Food f) { ... } public override void Eat(Apple a) { ... } }
The question is why giraffe.Eat(apple) resolved by Giraffe.Eat(Food) , and not virtual Animal.Eat(Apple) .
This is a consequence of two rules:
(1) The type of receiver is more important than the type of any argument in resolving overloads.
I hope this is clear why this should be so. The person who writes the derived class has more knowledge than the person who writes the base class, because the person who writes the derived class used the base class, and not vice versa.
The person who wrote Giraffe said: "I have a way for Giraffe eat any food," and this requires special knowledge about the internal digestion of the giraffe. This information is missing from the base class implementation, which only knows how to eat apples.
Thus, overload resolution should always be a priority for choosing the applicable method of the derived class when choosing the method of the base class, regardless of how efficient the conversion of the argument type is.
(2) The choice of overriding or not overriding the virtual method is not part of the total surface area of the class. This is a private implementation. Therefore, when deciding on an overload resolution, no decision should be made, depending on whether the method is overridden or not.
Overload resolution should never say, "I'm going to select Virtual Animal.Eat(Apple) because it has been overridden."
Now you can say "OK, suppose I am in the Giraffe when I call." The code inside Giraffe has all the knowledge of private implementation details, right? Thus, the solution Animal.Eat(Apple) instead of Giraffe.Eat(Food) called instead of giraffe.Eat(apple) , right? Because he knows that there is a realization that understands the needs of giraffes who eat apples.
This medicine is worse than a disease. Now we have a situation where identical code has a different behavior depending on where it is running! You can imagine calling giraffe.Eat(apple) outside the class, reorganizing it so that it is inside the class, and suddenly changing behaviors!
Or, you can say, hey, I understand that my Giraffe logic is actually quite general to go to the base class, but not to Animal, so I'm going to reorganize my Giraffe code to:
class Mammal : Animal { public void Eat(Food f) { ... } public override void Eat(Apple a) { ... } } class Giraffe : Mammal { ... }
And now all calls to giraffe.Eat(apple) inside Giraffe unexpectedly have different behavior when overloaded after refactoring? That would be very unexpected!
C # is the language of successful success; we really want to make sure that simple refactoring, such as changing the place in the hierarchy, the method is redefined, do not cause subtle changes in behavior.
Summarizing:
- Overload resolution prioritizes receivers for other arguments, because calling a specialized code that knows the internals of a receiver is better than calling a more general code that does not.
- Whether and when the method is redefined when overload resolution is resolved; all methods are treated as if they were never overridden for the purpose of overload resolution. This is an implementation detail, not part of a type surface.
- Overload resolution issues resolved - modulo availability of course! - the same, no matter where the problem occurs in the code. We do not have one resolution algorithm, where the receiver is of the type containing the code, and the other is when the call is in another class.
Additional thoughts on related issues can be found here: https://ericlippert.com/2013/12/23/closer-is-better/ and here https://blogs.msdn.microsoft.com/ericlippert/2007/09/04 / future-breaking-changes-part-three /