user234765
user234765

Reputation:

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

In this example, I'm attempting to pass by value, but the reference is passed instead.

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

This can be remedied like so:

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

What's is going on here? Why does the original example pass the reference?

Upvotes: 12

Views: 4901

Answers (6)

Matthew Sposato
Matthew Sposato

Reputation: 1635

When using an anonymous delegate or a lambda expression a closure is created so outside variables can be referenced. When the closure is created the stack (value) variables are promoted to the heap.

One way to avoid this is to start the thread with a ParameterizedThreadStart delegate. E.G.:

        static void Main()
    {

        for (int i = 0; i < 10; i++)
        {
            bool flag = false;

            var parameterizedThread = new Thread(ParameterizedDisplayIt);
            parameterizedThread.Start(flag);

            flag = true;
        }

        Console.ReadKey();
    }

    private static void ParameterizedDisplayIt(object flag)
    {
        Console.WriteLine("Param:{0}", flag);
    }

Coincidentally I ran into this concept just yesterday: Link

Upvotes: 0

Reed Copsey
Reed Copsey

Reputation: 564333

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.

Upvotes: 18

Michael Stum
Michael Stum

Reputation: 180884

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

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

Upvotes: 2

codekaizen
codekaizen

Reputation: 27419

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.

Upvotes: 1

Vladimir Gritsenko
Vladimir Gritsenko

Reputation: 1673

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

Upvotes: 3

mqp
mqp

Reputation: 71937

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.

Upvotes: 19

Related Questions