38

So for instance you have a type like:

public class EffectOptions
{
    public EffectOptions ( params object [ ] options ) {}

    public EffectOptions ( IEnumerable<object> options ) {}

    public EffectOptions ( string name ) {}

    public EffectOptions ( object owner ) {}

    public EffectOptions ( int count ) {}

    public EffectOptions ( Point point ) {}

}

Here I just give the example using constructors but the result will be the same if they were non-constructor methods on the type itself, right?

So when you do:

EffectOptions options = new EffectOptions (null);

which constructor would be called, and why?

I could test this myself but I want to understand how the overload resolution system works (not sure if that's what it's called).

Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • If it's not ambiguous, then it will call the "most specific" constructor. How "most specific" is defined is in the spec. See http://stackoverflow.com/questions/3674368/overload-resolution-and-virtual-methods for related information and links to the relevant (at least *some* relevant) parts of the specification. – Jim Mischel Mar 02 '11 at 21:01

2 Answers2

86

For the exact rules, see the overload resolution spec. But briefly, it goes like this.

First, make a list of all the accessible constructors.

public EffectOptions ( params object [ ] options )
public EffectOptions ( IEnumerable<object> options ) 
public EffectOptions ( string name )
public EffectOptions ( object owner ) 
public EffectOptions ( int count ) 
public EffectOptions ( Point point )

Next, eliminate all the inapplicable constructors. An applicable constructor is one where every formal parameter has a corresponding argument, and the argument is implicitly convertible to the formal parameter type. Assuming that Point is a value type, we eliminate the "int" and "Point" versions. That leaves

public EffectOptions ( params object[] options )
public EffectOptions ( IEnumerable<object> options ) 
public EffectOptions ( string name )
public EffectOptions ( object owner ) 

Now, we have to consider whether the one with "params" is applicable in its expanded or unexpanded form. In this case it is applicable in both forms. When that happens, we discard the expanded form. So that leaves

public EffectOptions ( object[] options )
public EffectOptions ( IEnumerable<object> options ) 
public EffectOptions ( string name )
public EffectOptions ( object owner ) 

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. Giraffe is more specific than Mammal, Mammal is more specific than Animal, Animal is more specific than object.

The object version is less specific than all of them, so it can be eliminated. The IEnumerable<object> version is less specific than the object[] version (do you see why?) so it can be eliminated too. That leaves

public EffectOptions ( object[] options )
public EffectOptions ( string name )

And now we are stuck. object[] is neither more nor less specific than string. Therefore this gives an ambiguity error.

That is just a brief sketch; the real tiebreaking algorithm is much more complicated. But those are the basics.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Thanks Eric, appreciate the response. I am not exactly sure why object[] is more specific than IEnumerable. Is it because IEnumerable is using generics? – Joan Venge Mar 02 '11 at 23:33
  • 19
    @Joan: Every giraffe is an animal, but not every animal is a giraffe. Therefore giraffe is more specific than animal. Every object[] is an IEnumerable, but not every IEnumerable is an object[]. Therefore object[] is more specific than IEnumerable. – Eric Lippert Mar 02 '11 at 23:49
  • Thanks Eric, I never thought of it that way. – Joan Venge Mar 03 '11 at 00:01
  • @EricLippert, how does generics methods come into play, (prior to c# 7.3) were generics never ruled out because T is always the most specific because it's in reference to the type – johnny 5 May 17 '19 at 21:00
  • @johnny5: Sorry, I don't understand the question, and it sounds like the question is too complicated to explain in a comment. Why not create a new question? This is a question-and-answer site after all! :-) – Eric Lippert May 17 '19 at 21:05
  • I have a new question despite the accepted answer my coworker refused to believe that this follows spec https://stackoverflow.com/q/56192446/1938988 – johnny 5 May 17 '19 at 21:06
  • @johnny5: Jon is of course right; the overload resolution rules changed slightly in 7.3. Any time you are making a choice between "show an error because what the developer said was unclear" and "don't show an error, and try to work out what the developer meant", there will be people who are confused or disappointed by the choice you pick, no matter which it is! – Eric Lippert May 17 '19 at 21:09
  • @johnny5: If people refuse to believe what the specification clearly says then there is not much you can do to argue with them; those people are not amenable to reasonable persuasion. – Eric Lippert May 17 '19 at 21:10
  • Thanks for the clarification, He was trying to claim that its generics are less specific. My claim is that they are more specific because they are meant for that type explicitly, and they aren't ruled out because the where parameters are taken into consideration because they are not part of the formal parameter – johnny 5 May 17 '19 at 21:14
  • Well, the question you linked to is specifically about generic constraints. Let's consider a case with no constraints. If we have `M(T t)` and `M(Giraffe)` and you call `M(new Giraffe())` then it appears we have a conflict: `M(Giraffe)` and `M(Giraffe)` have the exact same formal parameter types. In this case there is a "tiebreaker round" where overload resolution says "Giraffe-that-comes-from-type-substitution is less specific than Giraffe-that-is-in-source-code-directly", so the latter would win. See the spec for details. So I can see where your coworker is coming from. – Eric Lippert May 17 '19 at 21:25
  • 1
    For the newer specification link, see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#overload-resolution – ezolotko Nov 17 '19 at 07:49
9

In this case the C# compiler won't pick any constructor and instead will error. The value null is legal for several of the available constructors and there is insufficient tie breaking logic to pick one hence it produces an error.

The C# compiler overload resolution logic is a complex process but a short (and inherently incomplete) overview of how it works is as follows

  • Collect all members with the given Name
  • Filter the members to those with parameter lists that are compatible with the provided arguments and with the appropriate accessibility
  • If the remaining members has more than one element, employ tie breaking logic to choose the best of them

The full details are listed in section 7.4 of the C# language spec. And I'm sure Eric will be along shortly to give a much more accurate description :)

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • Thanks Jared. What do you mean by "inherently incomplete"? You mean it's missing some functionality? – Joan Venge Mar 02 '11 at 21:04
  • @Joan, I mean that my 3 bullet item summary doesn't contain lots of little details inherent to overload resolution: operators, named + optional argument resolution, issues with overrides vs new, etc ... The C# lang spec has the full details (and takes about 4-5 pages to explain it all). – JaredPar Mar 02 '11 at 21:06