动作/ Lambda表达式内存管理问题

时间:2011-06-01 14:40:23

标签: c# memory-management .net-4.0

我在一个局部变量中存储一个动作,然后我在该局部变量超出范围之后使用。在使用它之前是否有被清理的危险?这是一个例子:

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

因此,您可以在此示例中看到该函数正在返回调用baseMap的操作列表。 baseMap只是一个局部变量。事实是,在其他操作中调用它足以让.NET知道不清理它吗?

4 个答案:

答案 0 :(得分:13)

我推荐你参考C#4规范的第5.1.7节,其中说:

  

如果局部变量由匿名函数捕获,则其生存期至少会延长,直到从匿名函数创建的委托或表达式树以及引用捕获变量的任何其他对象都有资格进行垃圾回收。

即使控制权超过了本地范围的末尾,也会延长本地的生命周期。在实践中,我们通过将local转换为闭包类的字段,然后通过在委托(或表达式树)中引用它来使类保持活动来实现此目的。

请注意,你可能遇到相反的问题;有时事情比你想要的更长寿命:

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

今天在C#中的工作方式是为两个代表生成的闭包只要生命周期较长的委托的生命周期保持“便宜”和“昂贵”,即使生命周期较长的委托实际上并不存在用“贵”! (VB,JScript和许多其他有闭包的语言也存在这个问题。)

我们在这里可以做的是在编译器中检测这种情况并创建两个闭包。我们正在考虑为未来版本的C#做这件事。

答案 1 :(得分:5)

不,它没有危险。一个匿名方法,使用外部的局部变量将匿名方法编译成一个新类,其中包含一个保存该局部变量的字段和一个与匿名方法相对应的方法。

在您的情况下,这将创建如下内容:

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

列表初始化将如下所示:

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

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

顺便说一句:你的语法有点偏。列表创建将无法编译。它应该是这样的:

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

答案 2 :(得分:1)

它没有超出范围,因为它被列表引用,因此它不会被垃圾收集器清理。

所以不,你没有危险

答案 3 :(得分:0)

委托是引用类型,在根目录中至少有一个对它们的引用之前不会进行清理。因此,如果您创建了Action委托,并且您将从方法中传递委托,请不要担心清理。