Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Are Some Closures 'Friendlier' Than Others?

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

like image 396
Rob P. Avatar asked May 07 '14 20:05

Rob P.


People also ask

Are closures better than Frontals?

A lace frontal or a lace closure? The truth is, both are great. Both will give you a full and flawless install with the illusion that hair is growing directly from your scalp. Closures and Frontals allow you to braid and protect your natural hair.

Do closures last longer than Frontals?

Lace closures last for longer Whether you're going for a sew-in or lace closure wig, they typically last longer than a frontal, with proper maintenance. Lace closures are more suited for warmer weather.

Do closures damage hair?

Beneficial: Lace closure doesn't cause any harm to your hairline and scalp. Because you can put it off easily at the end of day so that your hair can breathe. Also lace closure protects your hair from heat styling, backcombing, etc.


2 Answers

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 
like image 134
Chris Sinclair Avatar answered Oct 04 '22 03:10

Chris Sinclair


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?

like image 20
Eric Lippert Avatar answered Oct 04 '22 05:10

Eric Lippert