tags:

views:

223

answers:

5

I just came across the most unexpected behavior. I'm sure there is a good reason it works this way. Can someone help explain this?

Consider this code:

var nums = new int[] { 1, 2, 3, 4 };
var actions = new List<Func<int>>();

foreach (var num in nums)
{
    actions.Add(() => num);
}

foreach (var num in nums)
{
    var x = num;
    actions.Add(() => x);
}

foreach (var action in actions)
{
    Debug.Write(action() + " ");
}

The output is a bit surprising for me:

4 4 4 4 1 2 3 4 

Obviously there's something going on with how the lambda is referencing the enumerator. In the first version of the foreach, is 'num' actually bound to 'Current', instead of the result returned by it?

+2  A: 

See Eric Lippert's blog post on this issue; it has to do with how iterator variables are scoped in code, and how that applies to lambda closures and hoisted functions.

Adam Maras
Is it me or the link is dead? I would have liked to read what Eric Lippert has to say about this topic.
Will Marcouiller
I mispasted the URL. It's now fixed.
Adam Maras
yup....server error 404 here
hallie
@hallie try again now :)
Adam Maras
Excellent! Thanks for the link! =) Eric has always interesting technical stuff to read about.
Will Marcouiller
+6  A: 

This is well-known and established behavior regarding lambdas, though frequently surprising to those who've encountered it for the first time. The fundamental issue is that your mental model of what a lambda is isn't quite correct.

A lambda is a function that doesn't get run until it's invoked. Your closure binds a reference to that lambda instance, not the value. When you execute your actions in your final foreach loop, that's the first time you're actually following the closed reference to see what it is.

In the first case, you're referencing sum, and at that point, sum is 4, so of course all your output is 4. In the second case, each lambda has been bound to a different value that was local to the loop each time, and that value isn't changed (it hasn't been GC'd solely because of the lambda reference.) therefore, you get the answer that you expect.

The closure over a local temporary value is actually the standard approach to capture a specific value from a point in time within the lambda.

Adam's link to Eric Lippert's blog provides a more in-depth (and technically accurate) description of what's going on.

Greg D
+1  A: 

This is because of two following things:
1) delegates save context (scope) of outside variables
2) first foreach cycle will compile in only one "num" variable declared.
3) lazy evaluation

Each delegated added in the first cycle will save the same num variable saved to the scope. Because of lazy evaluation you will run the delegates after the first cycle is finished, so num veraible, saved to the delegates' scope equals 4.

Andrew Bezzub
+1  A: 

Since the foreach construct is just syntactic sugar it is best to think of it in it's true form.

int num;
while (nums.MoveNext())
{
    num = nums.Current;
    actions.Add(() => num);
}

The lambda will capture the num variable so when you execute the lambda the latest value of num will be used.

ChaosPandion
A: 

See On lambdas, capture, and mutability

Brian