Outer Variable Trap
The "Outer Variable Trap" occurs when a developer expects the value of a variable to be captured by a lambda expression or anonymous delegate, when actually the variable is captured itself.
Example:
var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
actions.Add(() => Console.Write("{0} ", i));
}
foreach (var action in actions)
{
action();
}
Possible output #1:
0 1 2 3 4 5 6 7 8 9
Possible output #2:
10 10 10 10 10 10 10 10 10 10
If you expected output #1, you've fallen into the Outer Variable Trap. You get output #2.
Fix:
Declare an "Inner Variable" to be captured repeatedly instead of the "Outer Variable" which is captured only once.
var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
var j = i;
actions.Add(() => Console.Write("{0} ", j));
}
foreach (var action in actions)
{
action();
}
For more details, see also Eric Lippert's blog.
Something like
foreach (var s in strings)
var x = results.Where(r => (r.Text).Contains(s));
Will not give the results you're expecting because the Contains is not executed for each iteration. Assigning s to a temporary variable inside the loop will fix this, though.
It's worthy to note that this trap existed for foreach
loops too but has been changed since C# 5.0, i.e. inside foreach
loops closures now close over a fresh copy of the loop variable each time. So the below code:
var values = new List<int>() { 100, 110, 120 };
var funcs = new List<Func<int>>();
foreach (var v in values)
funcs.Add(() => v);
foreach (var f in funcs)
Console.WriteLine(f());
Prints 120 120 120
< C# 5.0, but 100 110 120
>= C# 5.0
However for
loops still behave the same way.
@dtb is correct (big +1), but it's important to note that this only applies if the scope of the closure extends outside the loop. For example:
var objects = new []
{
new { Name = "Bill", Id = 1 },
new { Name = "Bob", Id = 5 },
new { Name = "David", Id = 9 }
};
for (var i = 0; i < 10; i++)
{
var match = objects.SingleOrDefault(x => x.Id == i);
if (match != null)
{
Console.WriteLine("i: {0} match: {1}", i, match.Name);
}
}
This will print:
i: 1 match: Bill i: 5 match: Bob i: 9 match: David
ReSharper will warn about "Access to modified closure," which can be safely ignored in this case.