Deep immersion in closures

Consider the following code block:

int x = 1; D foo = () => { Console.WriteLine(x); x = 2; }; x = 3; foo(); Console.WriteLine(x); 

Yield: 3.2. I am trying to understand what happens behind the scenes when this code works.

The compiler generates this new class: enter image description here

The question is how to change the variable x . How x inside <> _ DiplayClass1 modifies x inside a program class. Does it do something like this backstage?

 var temp = new <>c_DisplayClass1(); temp.x = this.x; temp.<Main>b_0(); this.x = temp.x; 
+5
source share
5 answers

This helps to view the fully decompiled code:

 // Decompiled with JetBrains decompiler // Type: Program // Assembly: test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null // MVID: D26FF17C-3FD8-4920-BEFC-ED98BC41836A // Assembly location: C:\temp\test.exe // Compiler-generated code is shown using System; using System.Runtime.CompilerServices; internal static class Program { private static void Main() { Program.\u003C\u003Ec__DisplayClass1 cDisplayClass1 = new Program.\u003C\u003Ec__DisplayClass1(); cDisplayClass1.x = 1; // ISSUE: method pointer Action action = new Action((object) cDisplayClass1, __methodptr(\u003CMain\u003Eb__0)); cDisplayClass1.x = 3; action(); Console.WriteLine(cDisplayClass1.x); } [CompilerGenerated] private sealed class \u003C\u003Ec__DisplayClass1 { public int x; public \u003C\u003Ec__DisplayClass1() { base.\u002Ector(); } public void \u003CMain\u003Eb__0() { Console.WriteLine(this.x); this.x = 2; } } } 

In particular, see how Main rewritten:

  private static void Main() { Program.\u003C\u003Ec__DisplayClass1 cDisplayClass1 = new Program.\u003C\u003Ec__DisplayClass1(); cDisplayClass1.x = 1; // ISSUE: method pointer Action action = new Action((object) cDisplayClass1, __methodptr(\u003CMain\u003Eb__0)); cDisplayClass1.x = 3; action(); Console.WriteLine(cDisplayClass1.x); } 

You see that the affected x connected to the closure class generated from the code. The following line changes x to 3:

  cDisplayClass1.x = 3; 

And this is the same x that the method << 27> refers to.

+2
source

If you look at what is happening in Main , you will see the following:

 public static void Main(string[] args) { Program.<>c__DisplayClass0_0 <>c__DisplayClass0_ = new Program.<>c__DisplayClass0_0(); <>c__DisplayClass0_.x = 1; Action action = new Action(<>c__DisplayClass0_.<Main>b__0); <>c__DisplayClass0_.x = 3; action(); Console.WriteLine(<>c__DisplayClass0_.x); } [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public int x; internal void <Main>b__0() { Console.WriteLine(this.x); this.x = 2; } } 

It makes things more clear. You see that the raised member x set twice, once - 1 , and then 3 . Inside b__0 it is set to 2 again. So you see that the actual change is happening with the same member. This is what happens when you close variables. A valid variable gets discarded, not a value.

+3
source

Since x is a local variable, your method can be translated into something equivalent (but not equal):

 int x = 1; var closure = new <>c_DisplayClass1(); closure.x = x; closure.x = 3; // x = 3 closure.<Main>b_0(); // foo(); Console.WriteLine(closure.x); // Console.WriteLine(x) 

In other words, using the variable x is replaced by closure.x

+2
source

According to C # in a nutshell:

lambda expression can refer to local variables and parameters of the method in which it is defined (external variables).

Example:

 int factor = 2; Func<int, int> multiplier = n => n * factor; Console.WriteLine (multiplier (3)); // outputs 6 

Captured variables and closure:

External variables referenced by the lambda expression are called captured variables . A lambda expression that captures variables is called a closure .

Captured variables are evaluated when delegation is actually called, and not when variables have been captured

For instance:

 int factor = 2; Func<int, int> multiplier = n => n * factor; factor = 10; Console.WriteLine (multiplier (3)); // output is 30 

Lambda expressions can themselves update captured variables:

 int seed = 0; Func<int> natural = () => seed++; Console.WriteLine (natural()); // 0 Console.WriteLine (natural()); // 1 Console.WriteLine (seed); // 2 

Captured variables extend the life of the delegate.

In the following example, a local variable seed usually disappears from the scope when the execution completes naturally. But since the seed was captured, its service life extended to the resolution of the captured delegate, of course:

 static Func<int> Natural() { int seed = 0; return () => seed++; // Returns a closure } static void Main() { Func<int> natural = Natural(); Console.WriteLine (natural()); // 0 Console.WriteLine (natural()); // 1 } 

A local variable created within a lambda expression is unique when invoked with a delegate instance. If we reorganize our previous example to create seeds in the lambda expression, we get another (in this case, undesirable) result:

 static Func<int> Natural() { return() => { int seed = 0; return seed++; }; } static void Main() { Func<int> natural = Natural(); Console.WriteLine (natural()); // 0 Console.WriteLine (natural()); // 0 } 

Capture is internally implemented by β€œlifting” the captured variables into private class fields. When the method is called, an instance of the class and bound to the delegate member instance.

Capturing variable iterations

When you commit an iteration variable for a for loop, C # treats this variable as though it was declared outside the loop. This means that the same variable is captured at each iteration. The following program writes 333 instead of 012:

 Action[] actions = new Action[3]; for (int i = 0; i < 3; i++) actions [i] = () => **Console.Write (i)**; // closure here foreach (Action a in actions) a(); // 333 

Each closure (in bold) captures the same variable, i. (This actually does if you think that I am a variable whose value is stored between iterations of the loop; you can even explicitly change I inside the loop body if you want.) The consequence of this is that when delegates are called later, each delegate sees the value during the call is 3.

The above example equals this:

 Action[] actions = new Action[3]; int i = 0; actions[0] = () => Console.Write (i); i = 1; actions[1] = () => Console.Write (i); i = 2; actions[2] = () => Console.Write (i); i = 3; foreach (Action a in actions) a(); // 333 

Revert a change to a note in C # 5:

Prior to C # 5.0, foreach loops worked the same way.

Consider the following example:

 Action[] actions = new Action[3]; int i = 0; foreach (char c in "abc") actions [i++] = () => Console.Write (c); foreach (Action a in actions) a(); 

it will output ccc in C # 4.0 , but in C # 5.0 it will output abc .. p>

Quote from the book:

This caused considerable confusion: unlike the for loop, the iteration variable in the foreach loop is unchanged, and therefore one would expect it to be considered local to the loop body. The good news is that it has been fixed in C # 5.0, and the example above now writes "abc."

Technically, this is a violation of the change, since recompiling C # 4.0 to C # 5.0 may produce a different result. In general, the C # team is trying to avoid breaking changes; however, in this case, a β€œbreak” will almost certainly indicate an undetected error in the C # 4.0 program, and not an intentional dependence on the old behavior.

+1
source

What happens is that int x looks like a global variable, so you can reach / update its value inside foo() when you create an anonymous method like

  `D foo = () => { Console.WriteLine(x); x = 2; };` 

The method has not yet been launched, it will be launched immediately after calling foo() , so the output will be 3.2.

-2
source

All Articles