Action/Lambda Expression Memory Management Question

点点圈 提交于 2019-12-05 05:12:36

I refer you to section 5.1.7 of the C# 4 spec, which says:

If the local variable is captured by an anonymous function, its lifetime extends at least until the delegate or expression tree created from the anonymous function, along with any other objects that come to reference the captured variable, are eligible for garbage collection.

Even if control passes the end of the scope of the local, the lifetime of the local is extended. In practice we do this by turning the local into a field of a closure class, and then keeping the class alive by referencing it in the delegate (or expression tree).

Note though that you can run into the opposite problem; sometimes things live longer than you want them to:

Expensive expensive = new Expensive();
Cheap cheap = new Cheap();
Action longlived = ()=>M(cheap);
Action shortlived = ()=>M(expensive);

The way this works in C# today is the closure generated for the two delegates keeps both "cheap" and "expensive" alive as long as the lifetime of the longer-lived delegate, even though the longer-lived delegate does not actually use "expensive"! (VB, JScript and many other languages that have closures also have this problem.)

What we could do here is detect this situation in the compiler and create two closures. We're considering doing that for a future version of C#.

No, it is not in danger. An anonymous method that uses a local variable from outside that anonymous method will be compiled into a new class with a field that saves that local variable and a method that corresponds to the anonymous method.

In your case, this will create something like the following:

class ModifiedClosure
{
    private Action<Customer1, Customer2> _baseMap;
    public ModifiedClosure(Action<Customer1, Customer2> baseMap)
    {
        _baseMap = baseMap;
    }

    public void Method(SpecialCustomer1 c1, SpecialCustomer2 c2)
    {
        _baseMap(c1, c2);
        c2.SpecialProperty = c1.SpecialProperty;
    }
}

The list initialization will then look something like this:

Action<Customer1, Customer2> baseMap = (c1, c2) => c2.FirstName = c1.FirstName;

var list = new List<object>()
{
    (Action<SpecialCustomer1, 
            SpecialCustomer2>)(new ModifiedClosure(baseMap).Method),
    // ...
};

BTW: Your syntax is a bit off. The list creation won't compile. It should look like this:

var list = new List<object>()
{
    (Action<SpecialCustomer1, SpecialCustomer2>)((c1, c2) =>
    {
        baseMap(c1, c2);
        c2.SpecialProperty = c1.SpecialProperty;
    }),
    (Action<SpecialCustomer1, SpecialCustomer2>)((c1, c2) =>
    {
        baseMap(c1, c2);
        c2.SpecialProperty2 = c1.SpecialProperty2;
    })
};

It's not out of scope as it's referenced by your list thus it won't be cleaned up by the garbage collector.

So no, you're not in danger

The delegate is reference type and will not be cleaned up until there will be at least one reference to them from root. So if you create the Action delegate and you will pass the delegate out from method, don't worry about clean up.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!