It appears to me as though there is a bug/inconsistency in the C# compiler.
There certainly are bugs and inconsistencies in the compiler. You have not found one of them. The compiler is behaving completely correctly and according to spec in all these cases.
I'm doing my best to make sense of this very confusing question. Let me try to break it down into a series of questions.
Why does this succeed and call the first method?
public void SomeMethod(string message, object data);
public void SomeMethod(string message, params object[] data);
// ....
SomeMethod("woohoo", item);
(Presumption: that item is an expression of a compile-time type other than object[].)
Overload resolution must choose between two applicable methods. The second method is only applicable in its expanded form. A method that is applicable only in its expanded form is automatically worse than a method applicable in its normal form. Therefore the remaining better method is chosen.
Why does this fail with an ambiguity error?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", (T)item);
It is impossible to say because you do not say what "T" is. Where does T come from in this example? There are two type parameters named T declared; is this code in the context of one of those methods? Since those are different types both named T it could make a difference. Or is this yet a third type called T?
Since the question does not have enough information to answer it, I'm going to ask a better question on your behalf.
Why does this fail with an ambiguity error?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", "hello");
It doesn't. It succeeds. Type inference chooses "string" for T in both methods. Both generic methods are applicable; the second is applicable in its expanded form, so it loses.
OK, then why does this fail with an ambiguity error?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", null);
It doesn't. It fails with a "cannot infer T" error. There's not enough information here to determine what T is in either case. Since type inference fails to find an candidate method, the candidate set is empty and overload resolution has nothing to choose between.
So this succeeds because... ?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", (string)null);
Type inference infers that both methods are candidates when constructed with "string". Again, the second method is worse because it is applicable only in its expanded form.
What if we take type inference out of the picture? Why is this ambiguous?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod<string>("woohoo", null);
We now have three applicable candidates. The first method, the second method in its normal form, and the second method in its expanded form. The expanded form is discarded because expanded is worse than normal. That leaves two methods in their normal form, one taking string and the other taking string[]. Which is better?
When faced with this choice we always choose the one with the more specific type. If you said
public void M(string s) { ... }
public void M(object s) { ... }
...
M(null);
we'd choose the string version because string is more specific than object. Every string is an object but not every object is a string.
string is not convertible to string[]. string[] is not convertible to string. Neither is more specific than the other. Therefore this is an ambiguity error; there are two "best" candidates.
Then why does this succeed and what does it do?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod<object>("woohoo", null);
Again we have three candidates, not two. We automatically eliminate the expanded form as before, leaving two. Of the two methods in normal form which remain, which is better?
We must determine which is more specific. Every object array is an object, but not every object is an object array. object[] is more specific than object, so we choose to call the version that takes an object[]. We pass a null parameter array, which is almost certainly not what you intended.
This is why it is an extremely poor programming practice to make overloads like you are doing. You introduce potential for your users to run into all kinds of crazy ambiguities when you do this kind of stuff. Please do not design methods like this.
A better way to design this sort of logic is: (Note that I have not actually compiled this code, this is just off the top of my head.)
static string ToString<T>(T t)
{
return t == null ? "" : t.ToString();
}
static string ToString<T>(T t1, T t2)
{
return ToString<T>(t1) + ToString<T>(t2);
}
static string ToString<T>(T t1, T t2, params T[] rest)
{
string firstTwo = ToString<T>(t1, t2);
if (rest == null) return firstTwo;
var sb = new StringBuilder();
sb.Append(firstTwo);
foreach(T t in rest)
sb.Append(ToString<T>(t));
return sb.ToString();
}
Now every case is handled with reasonable semantics and decent efficiency. And for any given call site you can immediately predict precisely which method will be called; there are only three possibilites: one argument, two arguments or more than two arguments. Each is handled unambiguously by a particular method.