Since the compiler can implicitly convert the int to double, it chooses the B.Abc method. This is explained in this post by Jon Skeet (search for “implicit”):
The target of the method call is an expression of type Child, so the
compiler first looks at the Child class. There’s only one method
there, and it’s applicable (there’s an implicit conversion from int to
double) so that’s the one that gets picked. The compiler doesn’t
consider the Parent method at all.The reason for this is to reduce the risk of the brittle base class
problem…
More from Eric Lippert
As the standard says, “methods in a base class are not candidates if any method in a derived class is applicable”.
In other words, the overload resolution algorithm starts by searching
the class for an applicable method. If it finds one then all the other
applicable methods in deeper base classes are removed from the
candidate set for overload resolution. Since Delta.Frob(float) is
applicable, Charlie.Frob(int) is never even considered as a candidate.
Only if no applicable candidates are found in the most derived type do
we start looking at its base class.
Things get a little more interesting if we extend the example in your question with this additional class that descends from A:
class C : A {
public void Abc(byte b) {
Console.Write("C");
}
}
If we execute the following code
int i = 1;
b.Abc((int)1);
b.Abc(i);
c.Abc((int)1);
c.Abc(i);
the results are BBCA. This is because in the case of the B class, the compiler knows it can implicitly cast any int to double. In the case of the C class, the compiler knows it can cast the literal int 1 to a byte (because the value 1 fits in a byte) so C’s Abc method gets used. The compiler, however, can’t implicitly cast any old int to a byte, so c.Abc(i) can’t use C’s Abc method. It must use the parent class in that case.
This page on Implicit Numeric Conversions shows a compact table of which numeric types have implicit conversions to other numeric types.