Question

Let me apologize in advance - I'm probably butchering the terminology. I have a vague understanding of what a closure is, but can't explain the behaviour I'm seeing. At least, I think it's a closure issue. I've searched online, but haven't found the right keywords to get what I want.

Specifically - I have two blocks of code that are REALLY SIMILAR (at least to my eyes). First:

static void Main(string[] args)
{
    Action x1 = GetWorker(0);
    Action x2 = GetWorker(1);
}

static Action GetWorker(int k)
{
    int count = 0;

    // Each Action delegate has it's own 'captured' count variable
    return k == 0 ? (Action)(() => Console.WriteLine("Working 1 - {0}",count++))
                  : (Action)(() => Console.WriteLine("Working 2 - {0}",count++));
}

If you run this code and invoke x1() and x2() you'll see that they maintain a separate 'count' value.

    foreach(var i in Enumerable.Range(0,4))
    {
        x1(); x2(); 
    }

Outputs:

Working 1 - 0
Working 2 - 0
Working 1 - 1
Working 2 - 1
Working 1 - 2
Working 2 - 2
Working 1 - 3
Working 2 - 3

That makes sense to me and matches the explanations I've read. Behind the scenes a class is created for each delegate/action and the class is given a field to hold the value of 'count'. I went to bed feeling smart!

BUT THEN - I tried this very similar code:

    // x3 and x4 *share* the same 'captured' count variable
    Action x3 = () => Console.WriteLine("Working 3 - {0}", count++);
    Action x4 = () => Console.WriteLine("Working 4 - {0}", count++);

And (like the comment says) the behavior is completely different here. x3() and x4() seem to have the SAME count value!

Working 3 - 0
Working 4 - 1
Working 3 - 2
Working 4 - 3
Working 3 - 4
Working 4 - 5
Working 3 - 6
Working 4 - 7

I can see what's happening - but I don't really get why they are treated differently. In my head - I liked that original behaviour I was seeing, but the later example confuses me. I hope that makes sense. Thanks

Was it helpful?

Solution

Your first example had two different int count variable declarations (from the separate method calls). Your second example is sharing the same variable declaration.

Your first example would behave the same as the second example had int count been a field of your main program:

static int count = 0;

static Action GetWorker(int k)
{
    return k == 0 ? (Action)(() => Console.WriteLine("Working 1 - {0}",count++))
                  : (Action)(() => Console.WriteLine("Working 2 - {0}",count++));
}

This outputs:

Working 1 - 0
Working 2 - 1
Working 1 - 2
Working 2 - 3
Working 1 - 4
Working 2 - 5
Working 1 - 6
Working 2 - 7

You can simplify it without the ternary operator as well:

static Action GetWorker(int k)
{
    int count = 0;

    return (Action)(() => Console.WriteLine("Working {0} - {1}",k,count++));
}

Which outputs:

Working 1 - 0
Working 2 - 0
Working 1 - 1
Working 2 - 1
Working 1 - 2
Working 2 - 2
Working 1 - 3
Working 2 - 3

The main issue is that a local variable declared in a method (in your case int count = 0;) is unique for that invocation of the method, then when the lambda delegate is created, each one is applying closure around its own unique count variable:

Action x1 = GetWorker(0); //gets a count
Action x2 = GetWorker(1); //gets a new, different count

OTHER TIPS

A closure captures a variable.

A local variable is created when a method is activated by being called. (There are other things that create local variables but let's ignore that for now.)

In your first example you have two activations of GetWorker and therefore two completely independent variables named count are created. Each is captured independently.

In your second example, which unfortunately you do not show all of, you have a single activation and two closures. The closures share the variable.

Here's a way to think about it that might help:

class Counter { public int count; }
...
Counter Example1()
{
    return new Counter();
}
...
Counter c1 = Example1();
Counter c2 = Example1();
c1.count += 1;
c2.count += 2;
// c1.count and c2.count are different.

Vs

void Example2()
{
    Counter c = new Counter();
    Counter x3 = c; 
    Counter x4 = c;
    x3.count += 1;
    x4.count += 2;
    // x3.count and x4.count are the same.
}

Does it make sense to you why in the first example there are two variables called count that are not shared by multiple objects, and in the second there is only one, shared by multiple objects?

The difference is that in one example you have one delegate, the other you have two.

Since the count variable is local, it is regenerated each time you make the call. Since only one delegate is used (due to the ternary) each delegate gets a different copy of the variable. In the other example, both delegates get the same variable.

A ternary operator only returns one of its two arguments, so the closure works as you expect. In the second example, you create two closures that share the same "parent" count variable, giving the different result.

It might be a bit clearer if you look at it this way (this is equivalent code to your first sample):

static Action GetWorker(int k)
{
    int count = 0;
    Action returnDelegate

    // Each Action delegate has it's own 'captured' count variable
    if (k == 0)
         returnDelegate = (Action)(() => Console.WriteLine("Working 1 - {0}",count++));
    else
         returnDelegate = (Action)(() => Console.WriteLine("Working 2 - {0}",count++));

    return returnDelegate
}

Clearly there is only one closure generated here, and your other sample obviously has two.

Another alternative (of what perhaps you were looking for):

static Action<int> GetWorker()
{
    int count = 0;

    return k => k == 0 ? 
             Console.WriteLine("Working 1 - {0}",count++) : 
             Console.WriteLine("Working 2 - {0}",count++);
}

Then:

var x = GetWorker();

foreach(var i in Enumerable.Range(0,4))
{
    x(0); x(1);
}    

Or maybe:

var y = GetWorker();
// and now we refer to the same closure
Action x1 = () => y(0);
Action x2 = () => y(1);

foreach(var i in Enumerable.Range(0,4))
{
    x1(); x2(); 
}

Or maybe with some curry:

var f = GetWorker();
Func<int, Action> GetSameWorker = k => () => f(k);

//  k => () => GetWorker(k) will not work

Action z1 = GetSameWorker(0);
Action z2 = GetSameWorker(1);    

foreach(var i in Enumerable.Range(0,4))
{
    z1(); z2(); 
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top