C# Closures, why is the loopvariable captured by reference?

Well, that's just how C# works. The lambda expression in your statement constructs a lexical closure, which stores a single reference to i that persists even after the loop has concluded.

To remedy it, you can do just the thing that you did.

Feel free to read more on this particular issue all around the Web; my choice would be Eric Lippert's discussion here.


This is easier to understand if you look at what happens, in terms of scope:

for (int i = 0; i < 10; i++)
{
    Thread t = new Thread(() => new PhoneJobTest(i);    
    t.Start();
}

Basically translates to something very close to this:

int i = 0;
while (i < 10)
{
    Thread t = new Thread(() => new PhoneJobTest(i);    
    t.Start();
    i++;
}

When you use a lambda expression, and it uses a variable declared outside of the lambda (in your case, i), the compiler creates something called a closure - a temporary class that "wraps" the i variable up and provides it to the delegate generated by the lambda.

The closure is constructed at the same level as the variable (i), so in your case:

int i = 0;
ClosureClass = new ClosureClass(ref i); // Defined here! (of course, not called this)
while (i < 10)
{
    Thread t = new Thread(() => new PhoneJobTest(i);    
    t.Start();
    i++;
}

Because of this, each Thread gets the same closure defined.

When you rework your loop to use a temporary, the closure is generated at that level instead:

for (int i = 0; i < 10; i++)
{
    int jobNum = i;
    ClosureClass = new ClosureClass(ref jobNum); // Defined here!
    Thread t = new Thread(() => new PhoneJobTest(jobNum);    
    t.Start();
}

Now, each Thread gets its own instance, and everything works properly.


Short answer: closures. Long answer given here (among other places): Differing behavior when starting a thread: ParameterizedThreadStart vs. Anonymous Delegate. Why does it matter?


You definitely want to read Eric Lippert's "Closing over the loop variable considered harmful":

  • Part 1
  • Part 2

In Short: The behavior you see is exactly how C# works.


It happens because of the way C# passes parameters to a lambda. It wraps the variable access in a class which is created during compilation, and exposes it as a field to the lambda body.