Sean Blakemore's Blog

Like trying to fit a square peg in a round hole

9. May 2012 14:04
by Sean
7 Comments

What JavaScript taught me about C# – Understanding ‘Access to modified closure’

9. May 2012 14:04 by Sean | 7 Comments

Background

If you’ve spent any time writing C# using a copy of ReSharper you will have come across the phrase ‘Access to modified closure’. It’s a well known warning and there are plenty of StackOverflow questions and blog posts about it.

I’ve personally run up against this many times, and although I know what to do when it bites me, I’ve never really understood why it was happening. It just never really clicked for me.

Not until recently that is, when I decided it was time for me to become a JavaScript programmer instead of a jQuery programmer and actually learn the language properly!

The Warning

Here is some code, as simple as I could make it, that produces the warning and illustrates the behaviour:

var actions = new Action[5];

for (int i = 0; i < actions.Length; i++)
    actions[i] = () => Console.WriteLine(i);

foreach (var action in actions)
    action();

The ‘Access to modified closure’ is referring to the use of ‘i’, the loop variable, inside the lambda and being passed to ‘Console.WriteLine’. If you haven’t run into this before you’ll be surprised to find that this code will produce the following output:

5
5
5
5
5

The way to produce the behaviour you would have been expecting is to make a copy of the loop variable inside the loop and use that instead:

var actions = new Action[5];

for (int i = 0; i < actions.Length; i++)
{
    var local = i;
    actions[i] = () => Console.WriteLine(local);
}

foreach (var action in actions)
    action();

This will now produce:

0
1
2
3
4

Ok, that’s all well and good, we can now easily enough fix this problem if we run into it. Doesn’t it just feel a bit weird though? Why would making a copy of the variable and then passing that to ‘Console.WriteLine’ actually change the output?

Epiphany

Everywhere else I’ve read about this talks about ‘capturing variables’ or ‘closing over variables’. Sometimes all it takes is a particular combination of words, just the right phrasing, to make you feel that you really understand something.

It wasn’t quite correct to say that JavaScript taught me anything about C#, it was actually Douglas Crockford, it was in his book “The Good Parts” that I found just the right way of explaining closures. Here is the magic sentence:

“It is important to understand that the inner function has access to the actual variables of the outer functions and not copies”

So the lambdas have access to the actual variable ‘i’ and not a copy with the value at the time the lambda was created. Because the loop has finished and ‘i’ has been incremented to 5 by the time we execute the lambdas, that is what we see. On The other hand if we create a variable which is local to the loop then the lambda has access to the value at the time because the local variable is created for each iteration!

Now consider this code:

var i = 1;

Action action = () => Console.WriteLine(i);

i++;

action();

Which will output:

2

And this code:

var i = 1;

var j = i;
Action action = () => Console.WriteLine(j);

i++;

action();

Which will output:

1

Brilliant! How did I not fully grasp this before?

Breaking change in C# 5

Using Visual Studio 2010 and .Net 4, if we change the problem code to use a ‘foreach’ loop instead we have the same problem because the loop variable is reassigned for each iteration instead of being recreated:

var values = new[] { 0, 1, 2, 3, 4 };
var actions = new List<Action>();

foreach (var v in values)
{
    actions.Add(() => Console.WriteLine(v));
}

foreach (var action in actions)
    action();

And this will output:

4
4
4
4
4

It’s ‘4’ and not ‘5’ this time because in the ‘for’ the loop variable will be incremented to the point that the test in the second statement of the declaration fails and the loop exits.

This is as we would expect given the explanation above, however there is a twist! If you take this code and compile it Visual Studio 2011 using .Net 4.5 then you will get this following output:

0
1
2
3
4

Well what do you know, they “fixed” it. Not that it was really broken in the first place, just unexpected unless you properly understand closures. This was achieved by making it such that the variable is now logically declared inside the loop. Be aware however, they didn’t change the ‘for’ loop and that still works as before.

Comments (7) -

Thomas Levesque

It would be worth mentioning how they fixed it in C# 5... they changed the loop variable to be logically declared inside the loop instead than outside.

Sean

Hi Thomas,

Thanks, I've updated the post for the sake of clarity as per your suggestion. However, correct me if I'm wrong, but that was the only possible way they could have "fixed" it.

Tim Burga

Oh boy, so for and foreach loops will behave differently when closure modification is involved. Don't get me wrong - I appreciate the effort to shield less experienced users from this seemingly strange behavior, but I tend to think that consistency should have been favored over partial improvement in this case. I guess time will tell!

Sean

Yeah I agree with you completely, unexpected is better than inconsistent any day.

James Lewis

Massive light bulb moment after reading this!  Cheers

bruce zhou

I've had this issue before, If I'd seen this post earlier, I would not issue a thread (social.msdn.microsoft.com/.../f216aa00-5fde-44f4-ac5e-7d43a7cfeea5) in msdn forum.

Sean

Brilliant! Really glad it has helped...

Comments are closed