Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Action/Lambda Expression Memory Management Question

I'm storing an action in a local variable, then I'm using after that local variable is out of scope. Is it in danger of being cleaned up before I use it? Here is an example:

public List<object> GetMaps() {
    Action<Customer1, Customer2> baseMap = (Customer1 c1, Customer2 c2) => {
        c2.FirstName = c1.FirstName;
    };

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

    return list;
}

So, you can see in this example that the function is returning a list of actions that call baseMap. baseMap is just a local variable. Is the fact that it's called within the other actions enough for .NET to know not to clean it up?

like image 286
dontangg Avatar asked Jun 01 '11 14:06

dontangg


2 Answers

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#.

like image 55
Eric Lippert Avatar answered Oct 20 '22 03:10

Eric Lippert


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;
    })
};
like image 31
Daniel Hilgarth Avatar answered Oct 20 '22 02:10

Daniel Hilgarth