Tutorial :Outer Variable Trap



Question:

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


Solution:1

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.


Solution:2

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.


Solution:3

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.


Solution:4

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


Solution:5

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.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »