2

Sorry - not sure of a better name, please modify if you can think of better.

I am trying to learn a bit more about IEnumerable/collections/generics and I thought I was getting somewhere, until this example got me:

 var nums = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10 };

 var result = FilterNums(nums, i => i % 2 != 0);

.....

public static IEnumerable<T1> FilterNums<T1>(IEnumerable<T1> numslist, Func<T1, bool> predicateDelegate)

.....

Why does the call the call to FilterNums work? If I change it to FilterNums<int>, it still works and that is what I actually expected to type.

So, is it some how detecting the T1 for the lambda query and not requiring me to write it or is something else going on?

Wil
  • 10,234
  • 12
  • 54
  • 81
  • var is not a Variant type as you might find in other languages, the compiler just renames it for you to the right type during compilation. It does this via "type inference" (see Linkgoron's answer), in your example it cannot be anything else than `int` and therefore you do not need to explicitly specify this. – Bazzz Apr 02 '11 at 19:37

2 Answers2

8

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

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • You have explained very well... I thought it may be based on guessing from the other uses of T1 and now you have confirmed it! Everything interests me but I am still a newbie, I have bookmarked your links and will read them when I understand more. Many Many thanks. – Wil Apr 02 '11 at 20:35
  • Sorry if one of your links states this, however, I do not feel I am yet at the level where I can read the C# specification! I now fully understand this and why/how it works - I am very happy over your answer, but I was just wondering what best practices state? .... At the moment, I am only doing the most basic learning examples where I can not see a difference in speed, and I really like the fact this works, but, is there any overhead and/or should I put the type in if I know it in advance? – Wil Apr 03 '11 at 12:03
  • @Wil: The spec is not that scary. You can download it for free, or if you buy a printed copy it is full of annotations from C# experts on interesting facts about the language. The best practice here is to do whatever you feel is (1) correct -- if type inference gets it wrong then you have to put in the right type, and (2) whatever is easier to read. Sometimes having the type there makes the code more clear, and sometimes it's this ugly thing stuck in the middle of otherwise clean code. I tend towards the side of leaving it out and letting inference do the work. – Eric Lippert Apr 03 '11 at 15:58
  • you are right! Just had a quick look at the downloadable copy... I thought it would be a lot harder to understand! As for annotated, is this the book you mean? http://www.amazon.com/dp/0321741765 - Personally, just recently learning generics, the fact that it worked really confused me - but I also think it is easier to read in some situations, looking at your profile/job, I think it is safe for me to follow your advice and leave it out when it makes sense! :) Many thanks! – Wil Apr 03 '11 at 16:28
6

This is because of type inference.

The compiler understands that you're sending an int, so it implicitly sets the generic type to int. If you'll look in the intellisense you'll see your generic type is set as int.

Linkgoron
  • 4,866
  • 2
  • 26
  • 26
  • I know I will probably sound stupid, but it still confuses me a little... How does it understand I am sending it an int? .... Is it then looking at the first IEnumerable numslist and setting the T1 there or somewhere else? – Wil Apr 02 '11 at 19:46
  • @Wil http://stackoverflow.com/questions/479883/how-good-is-the-c-type-inference this might help you out. – Linkgoron Apr 02 '11 at 19:59