Overloading, generic type inference and the 'params' keyword
Asked Answered
A

1

5

I just noticed a strange behavior with overload resolution.

Assume that I have the following method :

public static void DoSomething<T>(IEnumerable<T> items)
{
    // Whatever

    // For debugging
    Console.WriteLine("DoSomething<T>(IEnumerable<T> items)");
}

Now, I know that this method will often be called with a small number of explicit arguments, so for convenience I add this overload :

public static void DoSomething<T>(params T[] items)
{
    // Whatever

    // For debugging
    Console.WriteLine("DoSomething<T>(params T[] items)");
}

Now I try to call these methods :

var items = new List<string> { "foo", "bar" };
DoSomething(items);
DoSomething("foo", "bar");

But in both cases, the overload with params is called. I would have expected the IEnumerable<T> overload to be called in the case of a List<T>, because it seems a better match (at least to me).

Is this behavior normal ? Could anyone explain it ? I couldn't find any clear information about that in MSDN docs... What are the overload resolution rules involved here ?

Amargo answered 30/11, 2009 at 15:39 Comment(0)
L
9

Section 7.4.3 of the C# 3.0 specification is the relevant bit here. Basically the parameter array is expanded, so you're comparing:

public static void DoSomething<T>(T item)

and

public static void DoSomething<T>(IEnumerable<T> item)

The T for the first match is inferred to be List<string> and the T for the second match is inferred to be string.

Now consider the conversions involved for argument to parameter type - in the first one it's List<string> to List<string>; in the second it's List<string> to IEnumerable<string>. The first conversion is a better than the second by the rules in 7.4.3.4.

The counterintuitive bit is the type inference. If you take that out of the equation, it will work as you expect it to:

var items = new List<string> { "foo", "bar" };
DoSomething<string>(items);
DoSomething<string>("foo", "bar");

At that point, there's only one applicable function member in each call.

Lavone answered 30/11, 2009 at 15:50 Comment(7)
Thanks Jon, that's exactly the explanation I needed. Is there some kind of workaround to make it work as I want ? If not I guess I'll have to use a different name...Amargo
Yes, I noticed that it works if I specify the generic type parameter explicitly. But I lose the benefit of type inference...Amargo
I would suggest using a different name, just for clarity - or if you're only going to use it with more than one item, you could do (T first, params T[] others)Lavone
Actually I would be (T first, T second, params T[] others) (with only one explicit parameter it changes nothing)Amargo
@Thomas: Doh - yes indeed :) (Well, it actually changes whether or not you can call it with an explicit type argument and no values, but other than that...)Lavone
Nice one Jon. A good question to ask here would be "what would you expect to happen if there was no IE<T> overload?" In that case, clearly the best behaviour is to infer that T is List<string>. An important design principle of type inference is that it gives the same results no matter what other candidates exist. It would be quite strange if type inference changed when you added new overloads!Pyrometer
There's another strange case which could confuse things here, which is DoSomething("foo") - in that case it looks like a clear-cut case where only the parameter array overload will work... but of course the IEnumerable<T> overload is applicable as well, with T=char. Always an easy one to miss :)Lavone

© 2022 - 2024 — McMap. All rights reserved.