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:
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;
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With