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.
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.
var =
syntax, what does that do? =P –
Inebriant Contains
is executed for each iteration, but s
will surprisingly always have the same value. –
Withstand 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.
This wiki article explaining the concept of closures is helpful.
Also, this article is really good from a more specific C# implementation.
Anyway, the tl;dr 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.
© 2022 - 2024 — McMap. All rights reserved.