Why does lambda expression in C # cause a memory leak?

Note. This is not just random useless code, it is an attempt to reproduce the problem with lambda expressions and memory leaks in C #.

Learn the following program in C #. This is a console application that is simple:

  • Creates a new object of type Test
  • Writes to the console that the object was created
  • Causes garbage collection
  • Wait for some user input
  • Closes

I run this program using JetBrains DotMemory, and I take two snapshots of memory: one after initializing the object, and the other after collecting it. I compare the snapshots and get what I expect: one dead object of type Test.

But here's the question: I create a local lambda expression inside the constructor of the objects, and I DO NOT USE IT ANYONE. This is just a local constructor variable. I run the same procedure in DotMemory, and suddenly I get an object of type Test + <> that survives garbage collection.

See the attached storage path report from DotMemory: the lambda expression has a pointer to the Test + <> object that is expected. But who has a pointer to a lambda expression and why is it stored in memory?

Also, this Test + <> object, I assume it is just a temporary object to store the lambda method and has nothing to do with the original test object, am I correct?

public class Test
{
    public Test()
    {
        // this line causes a leak
        Func<object, bool> t = _ => true;
    }

    public void WriteFirstLine()
    {
        Console.WriteLine("Object allocated...");
    }

    public void WriteSecondLine()
    {
        Console.WriteLine("Object deallocated. Press any button to exit.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var t = new Test();
        t.WriteFirstLine();
        Console.ReadLine();
        t.WriteSecondLine();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        Console.ReadLine();
    }
}

DotMemory Storage Path Report

+4
source share
2 answers

- (, dotpeek), , - :

public class Test {
    public Test() {
        if (Test.ChildGeneratedClass.DelegateInstance != null)
            return;
        Test.ChildGeneratedClass.DelegateInstance = 
            Test.ChildGeneratedClass.Instance.DelegateFunc;
    }

    public void WriteFirstLine() {
        Console.WriteLine("Object allocated...");
    }

    public void WriteSecondLine() {
        Console.WriteLine("Object deallocated. Press any button to exit.");
    }

    [CompilerGenerated]
    [Serializable]
    private sealed class ChildGeneratedClass {
        // this is what called Test.<c> <>9 in your snapshot
        public static readonly Test.ChildGeneratedClass Instance;
        // this is Test.<c> <>9__0_0
        public static Func<object, bool> DelegateInstance;

        static ChildGeneratedClass() {
            Test.ChildGeneratedClass.Instance = new Test.ChildGeneratedClass();
        }

        internal bool DelegateFunc(object _) {
            return true;
        }
    }
}

, , , singleton , , Func<object,bool DelegateFunc. , , , GC. , Test, , "".

+7

, , , - .

, Test() . , . this, , Test(). , . :

private static Func<object, bool> cachedT;

public Test()
{
    if (cachedT == null)
    {
        cachedT = _ => true;
    }
    Func<object, bool> t = cachedT;
}

, , , GC, Test . , , , , .

, . , True ( , ):

using System;

class Test
{
    private Func<object> CreateFunc()
    {
        return () => new object();
    }

    static void Main()
    {
        Test t = new Test();
        var f1 = t.CreateFunc();
        var f2 = t.CreateFunc();
        Console.WriteLine(ReferenceEquals(f1, f2));
    }
}

- () => this;, False.

+3

Source: https://habr.com/ru/post/1688241/


All Articles