50

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

funie200
  • 3,688
  • 5
  • 21
  • 34
GilliVilla
  • 4,998
  • 11
  • 55
  • 96
  • 3
    I had no idea what you were talking about until I googled it; In doing so, I found tons of explanations and examples (in C#), so what else are you looking for? – Marc Aug 05 '10 at 16:12
  • 2
    @Marc Maybe OP is one of those persons (there are more for sure, at least one said it explicitly many times) that want SO to have an answer for every possible relevant programming question. Answer for this one was apparently missing. – Maciej Hehl Aug 05 '10 at 16:20
  • 2
    @Maciej, Excellent. The master list is one step closer to completion! Web dominance, here we come! – Marc Aug 05 '10 at 16:22
  • @Maciej Thanks! That and I want to get the best answer from top experts rather than some Tom,Dick or Harry! Where else can I have top notch authors and thinkers...answering me in the best possible way :) And now if any body else Googles "Outer Variable Trap" ..you know what they are gonna get :) – GilliVilla Aug 05 '10 at 16:24
  • To OP. Maybe I just have a "being an ass day today", but I can't resist to point this out, sorry. Maybe I'll stop being an ass when I gain editing ability. The topic is just it, a topic. It's purpose is to inform readers what the question is about. If the question is short and whole fits into the topic, fine, but in the place for an actual question there should be a question. – Maciej Hehl Aug 05 '10 at 16:26
  • Learn something new every day, namely that there are more and more engineering terms I don't know of for things that are apparent after developing long enough.. – Jimmy Hoffa Aug 05 '10 at 17:19
  • possible duplicate of [C# Captured Variable In Loop](http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal Nov 02 '13 at 05:52
  • @MaciejHehl 7 years later I'm glad Gilli asked this question... – Sinjai Aug 18 '17 at 16:42

5 Answers5

68

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.

funie200
  • 3,688
  • 5
  • 21
  • 34
dtb
  • 213,145
  • 36
  • 401
  • 431
  • 2
    Interesting pun on the word "trap": The variable is captured (trapped), and you're caught by a problem (fallen into a trap) – James Curran Aug 05 '10 at 16:17
  • 5
    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 Aug 05 '10 at 16:27
  • So j is basically a 'fresh' variable, and therefore the k-th action contains a variable j_k bound to the k-th value assumed by the loop variable. As a result the expected behaviour is obtained. – Andrea Scarcella May 17 '14 at 16:40
5

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.

heisenberg
  • 9,665
  • 1
  • 30
  • 38
  • I'm not familiar with the `var =` syntax, what does that do? =P – Marc Aug 05 '10 at 16:19
  • Nitpick: `Contains` is executed for each iteration, but `s` will surprisingly always have the same value. – dtb Aug 05 '10 at 16:22
  • @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<> ) – heisenberg Aug 05 '10 at 16:24
4

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.

Saeb Amini
  • 23,054
  • 9
  • 78
  • 76
1

@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
  • 25,132
  • 10
  • 101
  • 150
0

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.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Heather
  • 2,602
  • 1
  • 24
  • 33