2012-11-29 23 views
250

我有以下代码:为什么ReSharper告诉我“隐式捕获闭包”?

public double CalculateDailyProjectPullForceMax(DateTime date, string start = null, string end = null) 
{ 
    Log("Calculating Daily Pull Force Max..."); 

    var pullForceList = start == null 
          ? _pullForce.Where((t, i) => _date[i] == date).ToList() // implicitly captured closure: end, start 
          : _pullForce.Where(
           (t, i) => _date[i] == date && DateTime.Compare(_time[i], DateTime.Parse(start)) > 0 && 
              DateTime.Compare(_time[i], DateTime.Parse(end)) < 0).ToList(); 

    _pullForceDailyMax = Math.Round(pullForceList.Max(), 2, MidpointRounding.AwayFromZero); 

    return _pullForceDailyMax; 
} 

现在,我已经添加了对ReSharper是在暗示改变该行的注释。这是什么意思,或为什么需要改变?

+6

MyCodeSucks请修复接受的答案:kevingessner的一个错误(如评论中所解释的)并将其标记为已接受将误导用户,如果他们没有注意到控制台的答案。 – Albireo

+0

如果你在try/catch之外定义你的列表,并且在try/catch中完成所有的添加,然后将结果设置为另一个对象,你也可以看到这个。在try/catch中移动定义/添加将允许GC。希望这是有道理的。 –

回答

345

该警告告诉您变量endstart保持活着,因为此方法内的任何lambda表达式都保持活动状态。

看看短期例子

protected override void OnLoad(EventArgs e) 
{ 
    base.OnLoad(e); 

    int i = 0; 
    Random g = new Random(); 
    this.button1.Click += (sender, args) => this.label1.Text = i++.ToString(); 
    this.button2.Click += (sender, args) => this.label1.Text = (g.Next() + i).ToString(); 
} 

我得到一个“隐式捕获关闭:G”警告在第一拉姆达。它告诉我,只要第一个lambda正在使用,g就不能是garbage collected

编译器为两个lambda表达式生成一个类,并将该类中的所有变量放入lambda表达式中。

所以在我的例子中gi是在同一个类中执行我的代表。如果g是一个拥有大量资源的重对象,那么垃圾回收器无法回收它,因为只要使用任何lambda表达式,此类中的引用仍然存在。所以这是潜在的内存泄漏,这就是R#警告的原因。

@splintor 如C#中的匿名方法总是存储在每个方法一类有两种方法可以避免这一点:

  1. 使用,而不是匿名一个实例方法。

  2. 将lambda表达式的创建拆分为两个方法。

+20

什么是可能的方法来避免这种捕获? – splintor

+1

感谢这个伟大的答案 - 我了解到,即使仅在一个地方使用非匿名方法,也有理由使用非匿名方法。 – ScottRhee

+1

@splintor实例化委托内的对象,或者将其作为参数传递。在上面的例子中,据我所知,期望的行为实际上是持有对“Random”实例的引用。 – Casey

26

同意Peter Mortensen。

C#编译器仅生成一个类型,该类型封装方法中所有lambda表达式的所有变量。

例如,给定源代码:

public class ValueStore 
{ 
    public Object GetValue() 
    { 
     return 1; 
    } 

    public void SetValue(Object obj) 
    { 
    } 
} 

public class ImplicitCaptureClosure 
{ 
    public void Captured() 
    { 
     var x = new object(); 

     ValueStore store = new ValueStore(); 
     Action action =() => store.SetValue(x); 
     Func<Object> f =() => store.GetValue(); //Implicitly capture closure: x 
    } 
} 

编译器会生成一种看起来像:

[CompilerGenerated] 
private sealed class c__DisplayClass2 
{ 
    public object x; 
    public ValueStore store; 

    public c__DisplayClass2() 
    { 
    base.ctor(); 
    } 

    //Represents the first lambda expression:() => store.SetValue(x) 
    public void Capturedb__0() 
    { 
    this.store.SetValue(this.x); 
    } 

    //Represents the second lambda expression:() => store.GetValue() 
    public object Capturedb__1() 
    { 
    return this.store.GetValue(); 
    } 
} 

而且Capture方法被编译为:

public void Captured() 
{ 
    ImplicitCaptureClosure.c__DisplayClass2 cDisplayClass2 = new ImplicitCaptureClosure.c__DisplayClass2(); 
    cDisplayClass2.x = new object(); 
    cDisplayClass2.store = new ValueStore(); 
    Action action = new Action((object) cDisplayClass2, __methodptr(Capturedb__0)); 
    Func<object> func = new Func<object>((object) cDisplayClass2, __methodptr(Capturedb__1)); 
} 

虽然第二个lambda不使用x,它不能被垃圾收集器编译为x被编译为lambda中使用的生成类的属性。

1

对于Linq to Sql查询,您可能会收到此警告。由于在该方法超出范围之后查询经常被实现,所以lambda的范围可能超过该方法。根据你的情况,你可能想要实现结果(即通过。ToList()),以允许在L2S lambda中捕获方法的实例变量上的GC。

21

警告是有效的,并在具有多个拉姆达方法显示,他们捕捉不同的值

当调用包含lambda表达式的方法,编译器生成的对象被实例化以:表示的lambda 代表所有值

  • 字段由任何那些lambda表达式捕获

    • 实例方法

    作为一个例子:

    class DecompileMe 
    { 
        DecompileMe(Action<Action> callable1, Action<Action> callable2) 
        { 
         var p1 = 1; 
         var p2 = "hello"; 
    
         callable1(() => p1++); // WARNING: Implicitly captured closure: p2 
    
         callable2(() => { p2.ToString(); p1++; }); 
        } 
    } 
    

    检查生成的代码这个类(整理了一点点):

    class DecompileMe 
    { 
        DecompileMe(Action<Action> callable1, Action<Action> callable2) 
        { 
         var helper = new LambdaHelper(); 
    
         helper.p1 = 1; 
         helper.p2 = "hello"; 
    
         callable1(helper.Lambda1); 
         callable2(helper.Lambda2); 
        } 
    
        [CompilerGenerated] 
        private sealed class LambdaHelper 
        { 
         public int p1; 
         public string p2; 
    
         public void Lambda1() { ++p1; } 
    
         public void Lambda2() { p2.ToString(); ++p1; } 
        } 
    } 
    

    LambdaHelper创建同时存储p1p2实例。

    想象一下:

    • callable1保持一个长期参照其说法,helper.Lambda1
    • callable2不保留其参数的引用,helper.Lambda2

    在这种情况下,参考helper.Lambda1也间接引用p2中的字符串,这意味着垃圾收集器将无法处理找到它。最坏的情况是内存/资源泄漏。或者,它可能使对象存活的时间比其他需要的时间更长,如果它们从gen0升级到gen1,可能会对GC产生影响。

  • 相关问题