What exactly is the Outer Variable Trap? Explanation and examples in C# are appreciated.
EDIT: Incorporating Jon Skeet's diktat :)
What exactly is the Outer Variable Trap? Explanation and examples in C# are appreciated.
EDIT: Incorporating Jon Skeet's diktat :)
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.
Another website here with (perhaps) an obscure example: http://www.albahari.com/nutshell/predicatebuilder.aspx
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.
@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.
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.