Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How closure in c# works when using lambda expressions?

Tags:

c#

lambda

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

They say that the following code :

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

is non deterministic and can produce the following answer :

0223557799

I thought that when one uses lambda expressions the compiler creates some kind of anonymous class that captures the variables that are in use by creating members like them in the capturing class. But i is value type, so i thought that he should be copied by value.

where is my mistake ?

It will be very helpful if the answer will explain how does closure work, how do it hold a "pointer" to a specific int , what code does generated in this specific case ?

like image 926
OopsUser Avatar asked Feb 19 '14 19:02

OopsUser


People also ask

What is a closure in C?

The key idea of a closure is that of a function expression with lexical access to variables declared in the enclosing scope whose lifetime can exceed the lifetime of that enclosing scope. This allows a convenient and efficient syntactic expression of a unit of work, a task in ISO/ IEC parlance.

Does C support closure?

Although C was created two decades after Lisp, it nonetheless lacks support for closures.

How are closures implemented?

Closures are typically implemented with a special data structure that contains a pointer to the function code, plus a representation of the function's lexical environment (i.e., the set of available variables) at the time when the closure was created.

How do closures work?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.


2 Answers

The key point here is that closures close over variables, not over values. As such, the value of a given variable at the time you close over it is irrelevant. What matters is the value of that variable at the time the anonymous method is invoked.

How this happens is easy enough to see when you see what the compiler transforms the closure into. It'll 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 a bit more clearly what's going on. There is one copy of the variable, and that variable has now been promoted to a field of a new class, instead of being a local variable. Anywhere that would have modified the local variable now modifies the field of this instance. We can now see why your code prints what it does. After starting the new thread, but before it can actually execute, the for loop in the main thread is going back and incrementing the variable in the closure. The variable that hasn't yet been read by the closure.

To produce the desired result what you need to do is make sure that, instead of having every iteration of the loop closing over a single variable, they need to each have a variable that they close over:

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

Now the copy variable is never changed after it is closed over, and our program will print out 0-9 (although in an arbitrary order, because threads can be scheduled however the OS wants).

like image 153
Servy Avatar answered Oct 24 '22 22:10

Servy


As Albahari states, Although the passing arguments are value types, each thread captures the memory location thus resulting in unexpected results.

This is happening because before the Thread had any time to start, the loop already changed whatever value that inside i.

To avoid that, you should use a temp variable as Albahari stated, or only use it when you know the variable is not going to change.

like image 31
Orel Eraki Avatar answered Oct 24 '22 20:10

Orel Eraki