Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep diving into the implementation of closures

Tags:

closures

c#

Consider the following code block:

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

x = 3;
foo();
Console.WriteLine(x);

The output is: 3,2. I'm trying to understand what happens behind the scenes when this code is running.

The compiler generates this new class: enter image description here

The question if how does the x variable get's changed. How does the x inside <>_DiplayClass1 is changing the x inside Program class. Is it doing something like this behind the scenes?

var temp = new <>c_DisplayClass1();
temp.x = this.x;
temp.<Main>b_0();
this.x = temp.x;
like image 692
boger Avatar asked Jul 23 '15 11:07

boger


2 Answers

If you'd look what happens in Main, you'd see this:

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;
    }
}

This makes things more clear. You see that the lifted x member gets set twice, once to 1 and then 3. Inside b__0, it gets set again to 2. Thus, you see that the actual change happens to the same member. That is what happens when you close over variables. The actual variable gets lifted, not it's value.

like image 155
Yuval Itzchakov Avatar answered Sep 28 '22 03:09

Yuval Itzchakov


It helps to look at the fully de-compiled 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;
    }
  }
}

Specifically, look at how Main got re-written:

  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 x being affected is attached 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 behind action is referring to.

like image 42
Atif Aziz Avatar answered Sep 28 '22 02:09

Atif Aziz