First example: null argument
In the first case, this will cause string overloading. null matches both object and string , but the string is a more specific / derived type. So he selects string .
Check Eric Lippert message How does the method overload processing system decide which method to call when passing null? for a more detailed explanation of this part of overload resolution.
Now we must determine the best candidates. The best seller rules are complex, but the short version is that the more specific is better than the less specific.
Second example: integer literal
In the second case, he will choose the first overload, because the letter 7 is int . If you used 7u , that would be uint , and therefore a second overload would be preferable.
Integer literals are of a clearly defined type (even if they allow more implicit conversions than normal integral values). You can use suffixes of type u for unsigned or l to influence this type for a long time. Or you can add an explicit cast.
Although usually int will not be implicitly converted to uint , it is an integer constant that is in the valid range of uint , and the C # compiler has an additional rule allowing implicit conversion between integers of a constant if the constant matches the target range.
Once again, Eric explains the details: Why does this implicit conversion from int to uint work?
A constant expression of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong if the value of the constant expression is within the range of the destination type. A constant expression of type long can be converted to type ulong if the value of the constant expression is not negative.
In both examples, one overload is certainly the best with the C # compiler, and therefore you are not getting an ambiguous overload error.
Personally, I think that the first example should give a warning, but either the C # team does not agree, or simply did not manage to add this heuristic.