The type inference process is a bit complicated; see chapter 7 of the C# spec for exact details.
Briefly it works like this.
When you invoke a method without a generic parameter list, first we make a set of all the accessible methods of that name.
Next we check to see if any of them are generic. If they are then we try to see if the generic type arguments can be inferred from the actual arguments. The inference process goes like this. Suppose you have an argument list A:
A: (nums, i => i%2 != 0)
and a list of formal parameter types P:
P: (IEnumerable<T1>, Func<T1, bool>)
and a set of generic type parameters X:
X: <T1>
The goal of type inference is to make inferences from every member of A to the corresponding member of P in order to deduce enough information about each member of X.
This particular problem is easy. From the first argument we see that the type of nums is int[]. We see that the first formal parameter in P is IEnumerable<T1>
. We know that int[] is convertible to IEnumerable<int>
, and therefore T1 might be int. We make a note of that fact.
And at this point, we're basically done. We have nothing about T1 that we can infer from the second argument/parameter pair. Type inference succeeds, and determines that
T1 is int. So we pretend that you called it with <int>
as the type argument list.
That was a very simple type inference problem. Consider this one:
A: (customers, c=>c.Name)
P: (IEnumerable<T>, Func<T, R>)
X: <T, R>
This is the problem you get when you do customers.Select(c=>c.Name)
.
What do we do? From the first argument we deduce "customers implements IEnumerable<Customer>
, therefore T is probably Customer". After making that deduction we can then say "c in the lambda is therefore Customer. Therefore this is a lambda from Customer to whatever the type of Customer.Name is. That's string. Therefore R is string".
See how in that case one inference had to be chained onto the next; we cannot simply do inference "in parallel" because one inference might depend on the results of another. Those dependencies can contain loops and other odd topologies. The exact details of how we make progress through that chain of dependencies is a bit complicated; see the spec for details.
We also have to deal with the case where two or more bounds are deduced for a type parameter, and whether those bounds are "upper", "lower" or "exact".
If this subject interests you, I've written about it extensively. See
http://blogs.msdn.com/b/ericlippert/archive/tags/type+inference/
for lots of articles on various aspects of type inference, not just generic method type inference. For a video of me explaining how method type inference works in C# 3.0, see:
http://wm.microsoft.com/ms/msdn/visualcsharp/eric_lippert_2006_11/EricLippert01.wmv