tags:

views:

105

answers:

3

I'm very new to lambda expressions in C#, and I'm having trouble conceptualizing how they are stored/retrieved in a collection.

I'm trying to programatically create a list of 10 Funcs x => x + 1, x => x + 2, etc. as a test. My desired output is 0,1,2,3,4,5,6,7,8,9

Here is my code for that:

 var list = new List<Func<int, int>>();
 for (int i = 0; i < 10; i++)
 {     
   Func<int, int> func = x => x + i;
   Console.WriteLine("a) " + func.Invoke(0)); //returns 0,1,2,3,4,5,6,7,8,9

   list.Add(func);
   Console.WriteLine("b) " + list[i].Invoke(0)); //returns 0,1,2,3,4,5,6,7,8,9
 }

 foreach (var func in list) //returns 10,10,10,10,10,10,10,10,10,10
   Console.WriteLine("c) " + func.Invoke(0)); 

 for(int i = 0; i < list.Count; i++) //returns 10,10,10,10,10,10,10,10,10,10
    Console.WriteLine("d) " + list[i].Invoke(0));

I get the same results when substituting a Func array for the List[Func].

What am I missing?

+6  A: 

Make i local to the lambda by copying it into a new variable:

var list = new List<Func<int, int>>();
for (int i = 0; i < 10; i++)
{
    var temp = i;
    Func<int, int> func = x => x + temp;
    Console.WriteLine("a) " + func.Invoke(0)); //returns 0,1,2,3,4,5,6,7,8,9

    list.Add(func);
    Console.WriteLine("b) " + list[i].Invoke(0)); //returns 0,1,2,3,4,5,6,7,8,9
}

foreach (var func in list) //returns 0,1,2,3,4,5,6,7,8,9
    Console.WriteLine("c) " + func.Invoke(0));

for (int i = 0; i < list.Count; i++) //returns 0,1,2,3,4,5,6,7,8,9
    Console.WriteLine("d) " + list[i].Invoke(0));
Ray Vernagus
Aha! Thank you sir.
bufferz
+6  A: 

I think you're seeing the behaviour described here:

http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

Dave
Thanks for posting that link. Interesting how c# is kind of locked into this caveat so devs who've depended on maintaining the last value of i (in my case) don't have their programs broken.
bufferz
Where by "interesting" you mean "freakin' annoying". :-)
Eric Lippert
+3  A: 

Your problem is that the lambda expression captures the variable you used to define it.

Since the entire loop the generated the list shares the same variable, all of the delegates will return 10.

To solve this problem, declare a separate variable inside the generating loop, assign it to i, then use it in the lambda expression.

For example:

var list = new List<Func<int, int>>();
for (int dontUse = 0; dontUse < 10; dontUse++) {   
 var i = dontUse;

 Func<int, int> func = x => x + i;
 list.Add(func);
}

foreach (var func in list) 
 Console.WriteLine("c) " + func(0));

For more information, see this blog post

By the way, when calling the delegates, you don't need to write func.Invoke(params); you can simply write func(params).

SLaks
It was a photo-finish for the 3 correct answers to my main question, but +1 for your last comment about func(params)Thanks!
bufferz