Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Captured Closure (Loop Variable) in C# 5.0

This works fine (means as expected) in C# 5.0:

var actions = new List<Action>(); foreach (var i in Enumerable.Range(0, 10)) {     actions.Add(() => Console.WriteLine(i)); } foreach (var act in actions) act(); 

Prints 0 to 9. But this one shows 10 for 10 times:

var actions = new List<Action>(); for (var i = 0; i < 10; i++) {     actions.Add(() => Console.WriteLine(i)); } foreach (var act in actions) act(); 

Question: This was a problem that we had in C# versions before 5.0; so we had to use a loop-local placeholder for the closure and it's fixed now - in C# 5.0 - in "foreach" loops. But not in "for" loops!

What is the reasoning behind this (not fixing the problem for for loops too)?

like image 933
Kaveh Shahbazian Avatar asked Apr 28 '13 15:04

Kaveh Shahbazian


People also ask

What is a captured variable?

A captured variable is one from the outside of your local class - one declared in the surrounding block. In some languages this is called a closure.

What are closures in C#?

A closure as a first-class function in C# A closure is a particular type of function that is intrinsically linked to the environment in which it is referenced. As a result, closures can use variables pertaining to the referencing environment, despite these values being outside the scope of the closure.


1 Answers

What is the reasoning behind this?

I'm going to assume you mean "why wasn't it changed for for loops as well?"

The answer is that for for loops, the existing behaviour makes perfect sense. If you break a for loop into:

  • initializer
  • condition
  • iterator
  • body

... then the loop is roughly:

{     initializer;     while (condition)     {         body;         iterator;     } } 

(Except that the iterator is executed at the end of a continue; statement as well, of course.)

The initialization part logically only happens once, so it's entirely logical that there's only one "variable instantiation". Furthermore, there's no natural "initial" value of the variable on each iteration of the loop - there's nothing to say that a for loop has to be of a form declaring a variable in the initializer, testing it in the condition and modifying it in the iterator. What would you expect a loop like this to do:

for (int i = 0, j = 10; i < j; i++) {     if (someCondition)     {         j++;     }     actions.Add(() => Console.WriteLine(i, j)); } 

Compare that with a foreach loop which looks like you're declaring a separate variable for every iteration. Heck, the variable is read-only, making it even more odd to think of it being one variable which changes between iterations. It makes perfect sense to think of a foreach loop as declaring a new read-only variable on each iteration with its value taken from the iterator.

like image 198
Jon Skeet Avatar answered Oct 09 '22 04:10

Jon Skeet