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.