19

I noticed this the other day, say you have two overloaded methods:

public void Print<T>(IEnumerable<T> items) {
    Console.WriteLine("IEnumerable T"); 
}
public void Print<T>(T item) {
    Console.WriteLine("Single T"); 
}

This code:

public void TestMethod() {  
    var persons = new[] { 
        new Person { Name = "Yan", Age = 28 },
        new Person { Name = "Yinan", Age = 28 } 
    };  
    Print(persons);
    Print(persons.ToList()); 
}

prints:

Single T
Single T

Why are Person[] and List<Person> better matched to T than they are to IEnumerable<T> in these cases?

Thanks,

UPDATE: Also, if you have another overload

public void Print<T>(List<T> items) {
    Console.WriteLine("List T");
}

Print(persons.ToList()); will actually print List T instead of Single T.

theburningmonk
  • 15,701
  • 14
  • 61
  • 104
  • 2
    Calling `Print(persons as IEnumerable);` should call the other method. The generic method lookup doesn't do an implicit cast from `Person[]` to `IEnumerable`. When you call `person.ToList()` your immediate type is a `List` (also requiring no implicit cast). – Joseph Yaduvanshi Feb 05 '11 at 23:24
  • +1 I've been fiddling with this issue for a bit. – Dan Lugg Jul 10 '13 at 19:10

2 Answers2

18

The first part of your question (without the List-specific overload) is easy. Let's consider the Array call, because it works the same for both calls:

First, type inference produces two possible generic implementations of the call: Print<Person[]>(Person[] items) and Print<Person>(IEnumerable<Person> items).

Then overload resolution kicks in and the first one wins, because the second requires an implicit conversion, where the first one does not (see §7.4.2.3 of the C# spec). The same mechanism works for the List variant.

With the added overload, a third possible overload is generated with the List call: Print<Person>(List<Person> items). The argument is the same as with the Print<List<Person>>(List<Person> items) but again, section 7.4.3.2 provides the resolution with the language

Recursively, a constructed type is more specific than another constructed type (with the same number of type arguments) if at least one type argument is more specific and no type argument is less specific than the corresponding type argument in the other.

So the Print<Person> overload is more specific than the Print<List<Person>> overload and the List version wins over the IEnumerable because it requires no implicit conversion.

TToni
  • 9,145
  • 1
  • 28
  • 42
3

Because the methods generated from the generics Print(Person[] item) and Print(List<Person> item) are a better match than IEnumerable<T>.

The compiler is generating those methods based on your type arguments, so the generic template Print<T>(T item) will get compiled as Print(Person[] item) and Print(List<Person> item) (well, whatever type represents a List<Person> at compilation). Because of that, the method call will be resolved by the compiler as the specific method that accepts the direct type, not the implementation of Print(IEnumerable<Peson>).

Matthew Abbott
  • 60,571
  • 9
  • 104
  • 129
  • in other words, Print will always be deemed the best match seeing as T can be anything? But if you add a Print(List items) method, that's the method that'll be called for Print(persons.ToList) – theburningmonk Feb 05 '11 at 22:19
  • Also, according to this blog post by Eric Lippert, that's not how C# generics work http://blogs.msdn.com/b/ericlippert/archive/2009/07/30/generics-are-not-templates.aspx what you described is templates in C++ – theburningmonk Feb 05 '11 at 22:30
  • The link posted by @theburningmonk is no longer valid; here's an update: https://web.archive.org/web/20151029232904/http://blogs.msdn.com/b/ericlippert/archive/2009/07/30/generics-are-not-templates.aspx – Glenn Slayden Aug 02 '22 at 08:09