Apologies if this question has been asked already, but suppose we have this code (I've run it with Mono 2.10.2 and compiled with gmcs 2.10.2.0):
using System;
public class App {
public static void Main(string[] args) {
Func<string> f = null;
var strs = new string[]{
"foo",
"bar",
"zar"
};
foreach (var str in strs) {
if ("foo".Equals(str))
f = () => str;
}
Console.WriteLine(f()); // [1]: Prints 'zar'
foreach (var str in strs) {
var localStr = str;
if ("foo".Equals(str))
f = () => localStr;
}
Console.WriteLine(f()); // [2]: Prints 'foo'
{ int i = 0;
for (string str; i < strs.Length; ++i) {
str = strs[i];
if ("foo".Equals(str))
f = () => str;
}}
Console.WriteLine(f()); // [3]: Prints 'zar'
}
}
It seems logical that [1] print the same as [3]. But to be honest, I somehow expected it to print the same as [2]. I somehow believed the implementation of [1] would be closer to [2].
Question: Could anyone please provide a reference to the specification where it tells exactly how the str variable (or perhaps even the iterator) is captured by the lambda in [1].
I guess what I am looking for is the exact implementation of the foreach loop.
You asked for a reference to the specification; the relevant location is section 8.8.4, which states that a "foreach" loop is equivalent to:
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
Note that the value v is declared outside the while loop, and therefore there is a single loop variable. That is then closed over by the lambda.
Because so many people run into this problem the C# design and compiler team changed C# 5 to have these semantics:
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded-statement
}
Which then has the expected behaviour -- you close over a different variable every time. Technically that is a breaking change, but the number of people who depend on the weird behaviour you are experiencing is hopefully very small.
Be aware that C# 2, 3, and 4 are now incompatible with C# 5 in this regard. Also note that the change only applies to foreach, not to for loops.
See http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ for details.
Commenter abergmeier states:
C# is the only language that has this strange behavior.
This statement is categorically false. Consider the following JavaScript:
var funcs = [];
var results = [];
for(prop in { a : 10, b : 20 })
{
funcs.push(function() { return prop; });
results.push(funcs[0]());
}
abergmeier, would you care to take a guess as to what are the contents of results?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With