Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda variable capture in loop - what happens here? [duplicate]

Tags:

c#

.net

lambda

I am trying to get my head around, what happens here ? What sort of code does the compiler produce?

public static void vc()
{
    var listActions = new List<Action>();

    foreach (int i in Enumerable.Range(1, 10))
    {
        listActions.Add(() => Console.WriteLine(i));
    }

    foreach (Action action in listActions)
    {
        action();
    }
}

static void Main(string[] args)
{ 
  vc();
}

output: 10 10 .. 10

According to this, a new instance of ActionHelper would be created for every iteration. So in that case, I would assume it should print 1..10. Can someone give me some pseudo code of what the compiler is doing here ?

Thanks.

like image 773
Everything Matters Avatar asked Oct 12 '12 14:10

Everything Matters


People also ask

How do you capture a variable in lambda function?

Much like functions can change the value of arguments passed by reference, we can also capture variables by reference to allow our lambda to affect the value of the argument. To capture a variable by reference, we prepend an ampersand ( & ) to the variable name in the capture.

What is capture in lambda function?

A lambda expression can refer to identifiers declared outside the lambda expression. If the identifier is a local variable or a reference with automatic storage duration, it is an up-level reference and must be "captured" by the lambda expression.

What is a lambda capture list?

The capture list defines the outside variables that are accessible from within the lambda function body. The only capture defaults are. & (implicitly capture the used automatic variables by reference) and. = (implicitly capture the used automatic variables by copy).

What is a captured variable?

A captured variable is a variable declared outside of the scope of a lambda expression, but used within the expression.


1 Answers

In this line

 listActions.Add(() => Console.WriteLine(i));

the variable i, is captured, or if you wish, created a pointer to the memory location of that variable. That means that every delegate got a pointer to that memory location. After this loop execution:

foreach (int i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}

for obvious reasons i is 10, so the memory content that all pointers present in Action(s) are pointing, becomes 10.

In other words, i is captured.

By the way, should note, that according to Eric Lippert, this "strange" behaviour would be resolved in C# 5.0.

So in the C# 5.0 your program would print as expected:

1,2,3,4,5...10

EDIT:

Can not find Eric Lippert's post on subject, but here is another one:

Closure in a Loop Revisited

like image 113
Tigran Avatar answered Sep 24 '22 13:09

Tigran