Sunday, October 20, 2019

Explain correct about anonyomous method become closure and GC matter

https://theartofdev.com/2012/10/17/anonymous-methods-under-the-hood/

Anonymous methods under the hood

 
To continue my previous post on a bug I encountered. This time anonymous methods.
The best way to learn what happens under the hood is to see the generated code, I like to use dotPeek as it has the same UX as Resharper.
Not to make this post super long I focus only on 3 examples and ignoring different scenarios (statics, delegates) as the result is not much different and not really interesting.

Example 1:

A simple case where the anonymous method is a simple method call and no use of scope variables.
The anonymous method is converted to regular method and the call creates a delegate instance to use for the event.
 

Example 2:

Here we use a variable 'num' from method scope inside the anonymous method.
The 'num' variable needs to be captured so it can be used inside the anonymous method, so what the compiler does is create a new private sealed class that embodies the anonymous method body and captures the used variables in class fields. So there is 'num' field in the class but also it captures the 'this' pointer because the anonymous method call instance method on 'Test2' class.
In the call instance of the private class is created, the 'this' and 'num' are set and the event is subscribed using the new class, so its GC reachable.
Important to note here that the 'this' reference is captured because of the instance method call inside the anonymous method, so you can easily think on scenario that keeps instance of the class GC reachable.
 

Example 3:

Just for the fun of it, what happens when you use anonymous method inside a loop.
Because the loop index is defined outside the scope of the loop the private class instance is also created in the outer scope and actually the loop uses the field of the private class as the loop indexer. So if the event is invoked all instances will have the last index. If we add another variable 'i2' and init it with the value of 'I' in every iteration the compiler will generate code that will create private class instance in every loop iteration and capture the value of 'i2' so that's why it work 🙂
 

Conclusion:

  1. The anonymous method body is converted to regular method, static, instance or new class depending on the scenario.
  2. When an anonymous method uses variable's from the method scope a new private sealed class is generated to capture the used variables.
  3. The generated class contains fields for every variable that is used in the anonymous method but defined in the method that defined the anonymous method.
  4. The generated class instance is reachable by the GC as long as the event class is reachable (the class that defines the event that was subscribed).
  5. All the local variables that were used inside the anonymous method will be GC reachable as long as the event class is reachable.
  6. Instance that creates anonymous method can be captured behind the scenes because of instance method call and therefor GC reachable.
     

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
public class  Test1     
{     
    public Test1()     
    {     
        EventClass c2 = new EventClass();     
        c2.Event1 += () => DoSomething(5);     
    }     
    private void DoSomething(int num)     
    {     
        Console.WriteLine("Test: " +  num);     
    }     
}     
REPORT THIS AD


Example 1 generated code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class  Test1     
{     
    public Test1()     
    {     
        base.u002Ector();     
        EventClass eventClass = new  EventClass();     
        eventClass.Event1 += new Action((object) this,  __methodptr(u003Cu002Ectoru003Eb__0));      
    }     
    private void DoSomething(int num)     
    {     
        Console.WriteLine("Test: " +  (object) num);     
    }     
    [CompilerGenerated]     
    private void u003Cu002Ectoru003Eb__0()     
    {     
        this.DoSomething(5);     
    }     
}     

Example 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class  Test2     
{     
    public Test2()     
    {     
        int num = 5;     
        EventClass c2 = new EventClass();     
        c2.Event1 += () => DoSomething(num);     
    }     
    private void DoSomething(int num)     
    {     
        Console.WriteLine("Test: " +  num);     
    }     
}     

Example 1 generated code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class  Test2     
{     
    public Test2()     
    {     
        base.u002Ector();     
        Test2.u003Cu003Ec__DisplayClass1  cDisplayClass1 = new Test2.u003Cu003Ec__DisplayClass1();     
        cDisplayClass1.u003Cu003E4__this =  this;     
        cDisplayClass1.num = 5;     
        EventClass eventClass = new  EventClass();     
        eventClass.Event1 += new  Action((object) cDisplayClass1, __methodptr(u003Cu002Ectoru003Eb__0));     
    }     
    private void DoSomething(int num)     
    {     
        Console.WriteLine("Test: " +  (object) num);     
    }     
    [CompilerGenerated]     
    private sealed class  u003Cu003Ec__DisplayClass1     
    {     
        public int num;     
        public Test2 u003Cu003E4__this;     
 
        public u003Cu003Ec__DisplayClass1()     
        {     
            base.u002Ector();     
        }     
 
        public void  u003Cu002Ectoru003Eb__0()     
        {     
            this.u003Cu003E4__this.DoSomething(this.num);     
        }     
    }     
}     
REPORT THIS AD
REPORT THIS AD


Example 3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class  Test3     
{     
    public Test3()     
    {     
        EventClass c2 = new EventClass();     
        for (int i = 0; i < 10; i++)     
        {     
            c2.Event1 += () =>  DoSomething(i);     
        }     
    }     
    private void DoSomething(int num)     
    {     
        Console.WriteLine("Test: " +  num);     
    }     
}     

Example 3 generated code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class  Test3     
{     
    public Test3()     
    {     
        base.u002Ector();     
        EventClass eventClass1 = new  EventClass();     
        Action action1 = (Action) null;     
        Test3.u003Cu003Ec__DisplayClass2  cDisplayClass2 = new Test3.u003Cu003Ec__DisplayClass2();     
        cDisplayClass2.u003Cu003E4__this =  this;     
        for (cDisplayClass2.i = 0;  cDisplayClass2.i < 10; ++cDisplayClass2.i)     
        {     
            EventClass eventClass2 =  eventClass1;     
            if (action1 == null)     
            {     
                // ISSUE: method pointer     
                action1 = new Action((object)  cDisplayClass2, __methodptr(u003Cu002Ectoru003Eb__0));     
            }     
            Action action2 = action1;     
            eventClass2.Event1 += action2;     
        }     
    }     
    private void DoSomething(int num)     
    {     
        Console.WriteLine("Test: " +  (object) num);     
    }     
    [CompilerGenerated]     
    private sealed class  u003Cu003Ec__DisplayClass2     
    {     
        public int i;     
        public Test3 u003Cu003E4__this;     
 
        public void  u003Cu002Ectoru003Eb__0()     
        {     
            this.u003Cu003E4__this.DoSomething(this.i);     
        }     
    }     
}     

No comments:

Post a Comment