52

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)?

Kaveh Shahbazian
  • 13,088
  • 13
  • 80
  • 139
  • 3
    Do you mean "what's the reasoning for it not being fixed for `for` loops as well"? – Jon Skeet Apr 28 '13 at 15:15
  • I'm surprised it even work in the first case... Since you run the Action after exiting the scope of foreach/for. The `var i` shouldn't exist anymore. To me, it's a very "dangerous" design. – LightStriker Apr 28 '13 at 15:17
  • 1
    @LightStriker: No; it's a feature. It's called a closure. – SLaks Apr 28 '13 at 15:18
  • @LightStriker: It's not dangerous at all. The behaviour is very precisely defined. It was just *badly* defined before C# 5. – Jon Skeet Apr 28 '13 at 15:18
  • @SLaks You can call it a feature all your want, to me it's a confusing syntax that assume a variable survive the destruction of a scope it was created in. – LightStriker Apr 28 '13 at 15:20
  • 3
    It doesn't assume.. it extends the lifetime of the `var i` depending on the scope of the lambdas IIRC – Lews Therin Apr 28 '13 at 15:21
  • 1
    @LightStriker: Reject it if you like, but be aware that you're rejecting the whole of LINQ, for one thing... – Jon Skeet Apr 28 '13 at 15:24
  • @JonSkeet What are you talking about? I don't remember a variable created within a LINQ statement ever leaving it. I use LINQ most of the time to do operation, such as sorting, over a collection that already exist. Can you give me an example of LINQ that allowed a variable created within it to exist its scope beside from a method returning a variable or changing a property of an object contained in a collection? – LightStriker Apr 28 '13 at 15:28
  • 3
    @LightStriker: Absolutely. For example: `public IEnumerable FindAdults(int minimumAge) { return people.Where(p => p.Age >= minimumAge); }` By the time that predicate is executed, the parameter won't be in scope any more. Without that ability to capture variables, LINQ would be massively weaker. – Jon Skeet Apr 28 '13 at 15:42
  • @JonSkeet I'm seeing a method calling another method which runs a lambda delegate. The stack is still intact when the Lambda is executed. minimumAge is created inside the scope of that method and is only destroyed once it returns the result of Where, which by that time LINQ is done doing its job. – LightStriker Apr 28 '13 at 15:48
  • 1
    @LightStriker: No - when do you think that predicate is actually *executed*? It won't have been executed by the time the method returns. – Jon Skeet Apr 28 '13 at 15:49
  • @JonSkeet Why not? If the code calling that method actually do something with the returning IEnumerable, you're saying it could not exist or that it would run only if I try to access its content? If I change minimumAge after the predicate is created, you're saying I could break it? That's a very dangerous behavior. – LightStriker Apr 28 '13 at 15:58
  • @LightStriker: You'd have to define what you mean by "break" - but I can still assure you that in the method I quoted, the predicate would not have been executed yet - so by the time it *was* executed, `minimumAge` would *only* exist because it had been captured. Don't forget that LINQ evaluates things lazily where possible - the return value of `Where` is just a query which "knows" how to filter... it hasn't done the filtering yet. Repeatedly claiming the behaviour is dangerous doesn't make it so, although the original behavior with `foreach` loops was annoying. – Jon Skeet Apr 28 '13 at 16:05
  • @JonSkeet If it's not evaluated yet, changing the value of minimumAge after the LINQ statement would mean LINQ would not return the data I expect it to return at the moment I queried it. Even more in a multithread environment, you could get some very weird result and very little way to track why you're not getting the result you want. – LightStriker Apr 28 '13 at 16:08
  • 2
    @LightStriker: Well in the method I showed, nothing else *could* change that variable, could it? It's a local variable to that method, as it's a parameter. But in other cases, yes that can happen if you play fast and loose. But I'd say the problem here is your *expectation* more than anything - you expect a behaviour which is contrary to the clearly-defined behaviour in the specification. – Jon Skeet Apr 28 '13 at 16:12
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/29062/discussion-between-lightstriker-and-jon-skeet) – LightStriker Apr 28 '13 at 16:14
  • When Eric Lippert announced that they'd be making the change to foreach [here](https://stackoverflow.com/a/8899347/120955), he referenced his blog post which [explains](https://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/#:~:text=We%20have%20this,to%20for%20loops.) why it makes sense for `for` loops to behave this way. – StriplingWarrior Mar 06 '23 at 23:29

1 Answers1

49

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.

Bob Horn
  • 33,387
  • 34
  • 113
  • 219
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 7
    (It's hard for me to argue with a high rank developer with a 559k beside his picture; yet:) Common sense (the part restricted to boundaries of my mind) tends to disagree and in many other languages you can see that for acts as both for and foreach. Let's rewrite the while loop this way: var x = initializer(); while(condition) { feed_x_to_body(body); x = iterator(); } and I expect that feed_x_to_body (which is a job for compiler) does it's job as expected (not as what is logical according to underlying implementation - that's of compiler's interest not me!). Maybe I am wrong; please guide more. – Kaveh Shahbazian Apr 28 '13 at 15:48
  • 2
    @KavehShahbazian: No, that's not how a `for` loop works. The fact that many `for` loops *happen* to declare exactly one variable in the initializer is irrelevant. The variable declared in the initializer *is* the variable which is used in the body of the loop - it's not that the body is given a variable *value*. In particular, the body of the loop can *modify* the variable as well, which would break your model of the world. – Jon Skeet Apr 28 '13 at 15:51
  • 1
    "Being able to modifying the variable" made it clear to me. Yet I think when I create a new scope (like a lambda body) the value of variable should become detached from it's main scope. That part still has not found it's place in my mind and It would be very kind of you to guide more about that part. But I mark your response as answer because it fits (current) C# world. Thanks; – Kaveh Shahbazian Apr 28 '13 at 16:00
  • 3
    @KavehShahbazian: Well, simply put "it doesn't"! In C#, the *variable* is captured, not its value. So the lambda expression can change the variable, and changes to the variable from outside the lambda expression can be seen within the lambda expression. – Jon Skeet Apr 28 '13 at 16:03