tags:

views:

216

answers:

6

What exactly is the Outer Variable Trap? Explanation and examples in C# are appreciated.

EDIT: Incorporating Jon Skeet's diktat :)

Eric Lippert on the Outer Variable Trap

+16  A: 

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.

dtb
Interesting pun on the word "trap": The variable is captured (trapped), and you're caught by a problem (fallen into a trap)
James Curran
Given that this is likely to become the accepted answer, any chance of you adding a link to Eric Lippert's blog post? http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx
Jon Skeet
A: 

Another website here with (perhaps) an obscure example: http://www.albahari.com/nutshell/predicatebuilder.aspx

Nicholas Hill
+2  A: 

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.

kekekela
I'm not familiar with the `var =` syntax, what does that do? =P
Marc
Nitpick: `Contains` is executed for each iteration, but `s` will surprisingly always have the same value.
dtb
@dtb @Marc Yeah, what dtb said. I'm probably not explaining it that well. var is just an alternative for explicitly declaring the return type (in real code I would actually declare the return type here but I'm not in front of an IDE and didn't feel like looking up what Where actually returns...I think its IEnumberable<> )
kekekela
A: 

@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.

TrueWill
A: 

This article explaining the concept of closures is helpful:

http://en.wikipedia.org/wiki/Closure_(computer_science)

Also, this article is really good from a more specific C# implementation:

http://blogs.msdn.com/b/abhinaba/archive/2005/08/08/448939.aspx

Anyway, the tl;lr is that variable scope is just as important in anonymous delegate or lambda expressions as is it anywhere else within your code -- the behavior just isn't as obvious.

Jon