How does closure in C # work when using lambda expressions?

In the following tutorial: http://www.albahari.com/threading/

They say the following code:

for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start(); 

is not deterministic and can give the following answer:

0223557799

I thought that when using lambda expressions, the compiler creates some kind of anonymous class that captures the variables that are used, creating members like them in the capture class. But i is a value type, so I thought it should be copied by value.

where is my mistake

It will be very useful if the answer explains how the closure works, how it holds the "pointer" to a specific int, what code is generated in this particular case?

+8
c # lambda
source share
3 answers

The key point here is that closures are closed over variables, not over values. Thus, the value of this variable at the moment you close it does not matter. The value of this variable is important when invoking an anonymous method.

How this happens is easy enough to see when you see that the compiler converts the closure. This will create something morally similar to this:

 public class ClosureClass1 { public int i; public void AnonyousMethod1() { Console.WriteLine(i); } } static void Main(string[] args) { ClosureClass1 closure1 = new ClosureClass1(); for (closure1.i = 0; closure1.i < 10; closure1.i++) new Thread(closure1.AnonyousMethod1).Start(); } 

So here we can see more clearly what is happening. There is one copy of the variable, and now this variable has been translated into the field of the new class instead of the local one. Anywhere that would change a local variable now changes the field of this instance. Now we can understand why your code prints what it does. After starting a new thread, but before its actual execution, the for loop in the main thread returns and increases the value of the variable in closing. A variable that has not yet been read by the closure.

To get the desired result, you need to make sure that instead of closing each iteration of the loop over one variable, they need each of them to have a variable that they close:

 for (int i = 0; i < 10; i++) { int copy = i; new Thread(() => Console.WriteLine(copy)); } 

Now the copy variable never changes after it is closed, and our program will print 0-9 (although in random order, since threads can be scheduled, but the OS wants it).

+4
source share

Like an Albahari state. Although passing arguments are value types, each thread captures a memory location , which leads to unexpected results.

This is because before Thread was supposed to start, the loop had already changed any value inside i .

To avoid this, you should use the temp variable as indicated by Albahari or use it only when you know that the variable will not change.

+2
source share

i in Console.Write(i) evaluates correctly when this statement is executed. This statement will be executed after the thread has been completely created and started and brought to this code. By this time, the cycle moves forward several times, and thus i can be any value by then. Closures, unlike ordinary functions, have the appearance of local variables of the function in which it is defined (which makes them useful and the way to shoot in the foot).

+1
source share

All Articles