Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does a lambda in C# bind to the enumerator in a foreach?

Tags:

c#

foreach

lambda

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?

like image 398
scobi Avatar asked Mar 10 '10 00:03

scobi


People also ask

Does C have lambda expression?

No, C doesn't have lambda expressions (or any other way to create closures). This is likely so because C is a low-level language that avoids features that might have bad performance and/or make the language or run-time system more complex.

How does a lambda expression work?

Lambda Expressions were added in Java 8. A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.

How do you write lambda in C++?

Creating a Lambda Expression in C++ auto greet = []() { // lambda function body }; Here, [] is called the lambda introducer which denotes the start of the lambda expression. () is called the parameter list which is similar to the () operator of a normal function.

Why do we need lambdas in C++?

Summary and References. We have seen that lambda is just a convenient way to write a functor, therefore we should always think about it as a functor when coding in C++. We should use lambdas where we can improve the readability of and simplify our code such as when writing callback functions.


Video Answer


1 Answers

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 num, and at that point, the value of num 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.

like image 114
Greg D Avatar answered Oct 15 '22 19:10

Greg D