7

This issue has caught me out once again. Could someone provide a technical explanation as to why the following code does not produce any warnings or errors. The question you have to ask yourself is (of course) do you feel lucky?

class Program
{
    static string Feeling(object o) { return "Lucky"; }
    static string Feeling(string s) { return "Unlucky"; }

    static void Main(string[] args)
    {
        Console.WriteLine("I feel " + Feeling(null));
    }
}

Bonus points awarded if you know which method will be called without running the code. And just to add insult, it doesn't just happen with null parameters:

class Program
{
    static string Feeling(int i) { return "Lucky"; }
    static string Feeling(uint i) { return "Unlucky"; }

    static void Main(string[] args)
    {
        Console.WriteLine("I feel " + Feeling(7));
    }
}
adelphus
  • 10,116
  • 5
  • 36
  • 46
  • What's there to explain? literal `null` is of type 'object'; literal `7` is of type `int`. Am I missing something? – Sergey Kalinichenko Jan 26 '12 at 11:22
  • 3
    @dasblinkenlight: The first uses the string overload. – George Duckett Jan 26 '12 at 11:22
  • 5
    @dasblinkenlight: No, `null` isn't of type object - it's of the null type, and implicitly convertible to any reference type or any nullable value type. – Jon Skeet Jan 26 '12 at 11:23
  • Ah @dasblinkenlight, I yearn for the time when you come across this issue in real life. – adelphus Jan 26 '12 at 11:28
  • 2
    Slight correction to previous comment: a null literal expression doesn't *have* a type explicitly; it's just implicitly convertible to any nullable type (whether that's a reference type or a nullable value type). – Jon Skeet Jan 26 '12 at 11:30
  • @adelphus I got burned with same-name overloads that take the same number of parameters back in my C++ days some 15 years ago, and I stopped using them in my code (I cannot even remember the details, just remember that the bug I coded wasn't easy to find). This minimizes the chances of me running into this issue in real life :) – Sergey Kalinichenko Jan 26 '12 at 11:35

2 Answers2

12

First example: null argument

In the first case it will call the string overload. null matches both object and string, but string is the more specific/derived type. Thus it chooses string.

Check Eric Lippert's post How does the method overload resolution system decide which method to call when a null value is passed? for a longer explanation for this part of overload resolution.

Now we must determine the best of the applicable candidates. The bestness rules are complicated, but the short version is that more specific is better than less specific.

Second example: integer literal

In the second case it'll choose the first overload, because the literal 7 is int. If you had used 7u it would have been uint and thus the second overload would be preferred.

Integer literals have a well defined type(even if they allow more implicit conversions than normal integral values). You can use suffixes like u for unsigned, or l for long to influence that type. Or you can add an explicit cast.

While normally an int wouldn't be implicitly convertible to uint, this is an integer constant which is in the valid range of uint, and the C# compiler has an extra rule to allow implicit conversions between integer constants, provided the constant fits the target's range.

One 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, provided 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, provided the value of the constant expression is not negative.


In both examples one overload is clearly the best, as far as the C# compiler is concerned, and thus you don't get an ambiguous overloading error.

Personally I think that the first example should give a warning, but either the C# team disagrees, or they simply didn't have time to add that heuristic.

Community
  • 1
  • 1
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • "...one overload is clearly the best". I perhaps agree with the object/string overload because of the specificity argument, but the integer one is completely ambiguous. Who decided that int is superior to uint? – adelphus Jan 26 '12 at 11:34
  • @adelphus: It's not superior, but `7` *is* an `int`, `7u` is a `uint`. Hover over the `7` in visual studio. – George Duckett Jan 26 '12 at 11:40
  • 1
    The `7` literal has type `int`. An `int` argument is already an `int`, and thus can be passed to an `int` parameter directly, whereas passing it to a `uint` parameter requires a conversion. Thus the `int` parameter is preferred. – CodesInChaos Jan 26 '12 at 11:41
  • OK, stay with me here: If 7 is an `int`, it will call the `fn(int)` method. Now remove the `fn(int)` method. Either 7 is now a `uint` or 7 is still an `int` but implicitly cast to a uint. But an `int` value is *not* implicitly convertible to a `uint` so if 7 is always an int, the fn(uint) call should cause an error. Therefore the compiler *must* be making an arbitrary decision about whether to treat 7 as an `int` or a `uint` - this is why the code should produce an Ambiguous Method error. Geddit? – adelphus Jan 26 '12 at 12:19
  • An `int` *constant* is implicitly convertible to `uint` if it's in the correct range. As I mentioned in my answer, integer constants allow more implicit conversions than normal integer values. Check http://stackoverflow.com/questions/9008637/why-does-this-implicit-conversion-from-int-to-uint-work/9008765#9008765 for more information on that part. – CodesInChaos Jan 26 '12 at 12:35
2

The simple answer is that it doesn't give any errors or warnings because it's entirely valid code by the C# spec.

The relevant section of the C# 4 spec is 7.5.3 in general (for the whole process), and 7.5.3.2 to determine which applicable function member is better when the first phase has found more than one. (The subsequent sections such as 7.5.3.5 give details about "better conversion targets" etc.)

Trying to explain the rules absolutely correctly but in a short space would be hard to say the least, unfortunately. I suggest you look through that bit of the spec yourself very carefully.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • The fact that it is valid code does not, IMHO, warrant not providing a warning. There is a lot of valid code out there which produces warnings, yet still executes as the developer intends. – adelphus Jan 26 '12 at 11:31
  • @adelphus: Well, it *could* give a warning, yes... it definitely *shouldn't* give an error. It's behaving according to the spec, after all. Would you want a warning whenever you were calling *any* overloaded method where there were at least two applicable function members? – Jon Skeet Jan 26 '12 at 11:34
  • @JonSkeet IMO the spec should have written in a way that this should give an error. I'd prefer a rule that requires the conversion from given to required type to be more direct, instead of a rule that requires the required type to be more specific. But of course it's far too late to change that now, and a warning is the best we can do. – CodesInChaos Jan 26 '12 at 11:38
  • @CodeInChaos: I wouldn't want a warning - almost every single-argument call to `Console.WriteLine` would give that warning, for example. – Jon Skeet Jan 26 '12 at 11:45
  • @JonSkeet I don't think a "more direct conversion" rule would cause many overload errors/warnings. There might be problems I didn't think of, in some complex scenarios, but I see no problems with simple ones. – CodesInChaos Jan 26 '12 at 11:50
  • @CodeInChaos: I'll believe it when I see a detailed spec which has taken all the corner cases into account, and is still at least as pleasant to use in all normal cases :) (Language design is hard, let's go shopping...) – Jon Skeet Jan 26 '12 at 11:54