I decided to talk about this example because somebody pointed out that the behavior was surprising, and somebody else said it would make a good blog post. I think it was Ray.

There were a couple of comments in the original post that referred to other languages - VB and C++. While C# does have a lot in common with C++, there are a lot of differences as well, and this is one such area.

The rule that governs this behavior is detailed in 7.5.5.1 of the language spec, if you want the exact details, though I find it a bit hard to understand at times, so I'll do it by example instead. In the first chunk of code, we had:

A.F(int i);
B.F(object o);

and we are calling with an int. We start at the most derived class and look for methods that match our invocation. If we find any in a specific class, then we look through all applicable methods in that class and choose the best one (or error out if there isn't a best one). If we don't find any in the current class, we try the next base class.

So, because there's a conversion from int to object, B.F(object) is applicable, and we choose B.F(object o) and don't even look at A.F(int i).

Similarly, in the second case, we choose B.F(int i) rather than A.F(short i).

But note that the method in the derived class has to be an applicable one. If we do something like:

using System;

public class A
{
 public void F(int i)
 {
  Console.WriteLine("A: {0}", i);
 }
}

public class B: A
{
 public void F(string s)
 {
  Console.WriteLine("B: {0}", s);
 }
}

public class Test
{
 public static void Main()
 {
  B b = new B();
  b.F(15);
 }
}

We will find out that B.F(string) isn't applicable, so we call A.F(int) instead.

So, why does C# behave this way? Well, it's because of versioning. Considering our first case again, but assume that A.F(int) wasn't there when we first developed our product. We would be calling B.F(object). If somebody comes along and adds A.F(int) later, that doesn't change our program behavior, but it would if we didn't have the rule.

Regardless of the behavior, it's a pretty bad idea to do overloaded methods where there are conversions between the types (ie short and int, or object and anything), because the conversions may cause the compiler to call a different method than you expected. If you must do this, it's a good idea to provide overloads for all the types, so that there isn't any chance of confusion.