The second call works as you expect, because val in the second method is of type Object[] , so in foreach , var v type of Object is easily inferred. There is no ambiguity.
The second call is ambiguous: Object and Object[] references may be null , so the compiler must guess what you mean (more on this below). null does not have its own type; if that were the case, you would need an explicit cast to do anything with it that would be unpleasant.
Overload resolution occurs at compile time, and not at run time. Overload resolution in a loop is independent of whether v sometimes null ; which will not be known before execution, long before the overload is resolved by the compiler. It is based on declared type v . The declared type v is inferred, not explicitly declared, but the fact is that it is known at compile time, when overloads are allowed.
In another call, in which you explicitly pass null , the compiler should deduce which overload you want to use with the algorithm ( here is the answer in a language that normal people can hope to understand ), which in this case comes with the wrong answer.
Of the two, he selects Object[] , because Object[] can be dropped to Object , but the opposite is not true - Object[] is "more specific" or more specialized. This is further from the root of the type hierarchy (or in plain English, in this case one of the Object types and the other not).
Why is specialization a criterion? The assumption is that for two methods with the same name it is assumed that the general type of the argument is common (you can impose something on Object ), and overloading with the type further towards the sheets the hierarchy type will be used to replace the method general case for certain specific cases: "Stick to this for everything, if it's not an array from Object , I need to do something else for arrays of objects."
This is not the only conceivable criterion, but I cannot think of any other half as good.
In this case, it looks contradictory, because you think of null as general, as something that could be: it is not even specifically Object . This is anything.
The following actions would work because here the compiler should not guess what you mean by null :
public static void Test(String name, Object[] val) { Console.WriteLine(2); Object dummy = null; Test(name, dummy); foreach (var v in val) { Test(name, v); } }
Short answer: Explicit nulls makes overload resolution a mess, to such an extent that sometimes I wonder if it might be a mistake on the part of the language developers to even try to figure them out (NB "Sometimes I wonder if it can ..." not is an expression of dogmatic confidence, the guys who developed the language are smarter than me).
The compiler is as smart as possible that itโs not very. He may have sporadic bouts of outright malice, but in this case, just good intentions went wrong.