为什么C#中的Lambda表达式会导致内存泄漏? [英] Why does a lambda expression in C# cause a memory leak?

查看:730
本文介绍了为什么C#中的Lambda表达式会导致内存泄漏?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:这不仅是一些随机的无用代码,也是尝试重现Lambda表达式和C#中的内存泄漏的问题.

Note: this is not just some random useless code, this is an attempt to reproduce an issue with lambda expressions and memory leaks in C#.

检查以下C#程序.这是一个控制台应用程序,只需:

Examine the following program in C#. It's a console application that simply:

  1. 创建一个Test类型的新对象
  2. 在控制台上写入对象已创建
  3. 调用垃圾回收
  4. 等待任何用户输入
  5. 闭嘴

我使用JetBrains DotMemory运行此程序,并拍摄了两个内存快照:一个在对象初始化后,另一个在对象被收集后.我比较了快照并得到了期望的结果:一个类型为Test的死对象.

I run this program using JetBrains DotMemory, and I take two memory snapshots: one after the object was initialized, and another after its been collected. I compare the snapshots and get what I expect: one dead object of type Test.

但这是一个难题:然后,我在对象的构造函数中创建一个本地lambda表达式,并且我在任何地方都不使用它.它只是一个局部构造函数变量.我在DotMemory中运行相同的过程,突然,我得到一个类型为Test +<>的对象,该对象可以在垃圾回收后幸免.

But here's the quandary: I then create a local lambda expression inside the object's constructor and I DO NOT USE IT ANYWHERE. It's just a local constructor variable. I run the same procedure in DotMemory, and suddenly, I get an object of type Test+<>, which survives garbage collection.

请参阅DotMemory随附的保留路径报告:lambda表达式具有指向Test +<对象的指针,这是预期的.但是谁有一个指向lambda表达式的指针,为什么将其保存在内存中?

See the attached retention path report from DotMemory: The lambda expression has a pointer to the Test+<> object, which is expected. But who has a pointer to the lambda expression, and why is it kept in memory?

另外,这个Test +<>对象-我假设它只是持有lambda方法的临时对象,与原始Test对象无关,对吗?

Also, this Test+<> object - I assume it is just temporary object to hold the lambda method, and has nothing to do with the original Test object, am I right?

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

推荐答案

如果使用某些东西(例如dotpeek)对代码进行反编译,则会看到编译器生成了以下内容:

If you decompile your code with something (like dotpeek), you will see that compiler generated something like this:

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

因此它创建了子类,将您的函数作为该类的实例方法,在 static 字段中创建了该类的单例实例,最后使用以下命令创建了 static 字段:您的Func<object,bool引用方法DelegateFunc.因此,GC不能收集编译器生成的那些静态成员也就不足为奇了.当然,这些对象不会为您创建的每个Test对象创建,只能创建一次,因此我不能真正称其为泄漏".

So it created child class, put your function as a instance method of that class, created singleton instance of that class in a static field and finally created static field with your Func<object,bool referencing method DelegateFunc. So no surprise that those static members generated by compiler cannot be collected by GC. Of course those objects are not created for each Test object you create, only once, so I cannot really call that a "leak".

这篇关于为什么C#中的Lambda表达式会导致内存泄漏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆