为什么ReSharper告诉我“含蓄地closures”?

我有以下代码:

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

现在,我在ReSharperbuild议的一个改变上添加了一条评论。 这是什么意思,或为什么需要改变? implicitly captured closure: end, start

警告告诉你,variablesendstart保持活着,因为这个方法内的任何lambdaexpression式都保持活着状态。

看看这个简短的例子

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

在第一个lambda中,我得到了“隐式捕获的闭包:g”警告。 它告诉我,只要第一个lambda被使用, g就不能被垃圾收集 。

编译器为两个lambdaexpression式生成一个类,并将所有variables放在lambdaexpression式中使用的类中。

所以,在我的例子中,我和g被关在同一个class级里执行我的代表。 如果g是一个有大量剩余资源的重对象,垃圾回收器将无法回收它,因为只要使用任何lambdaexpression式,此类中的引用仍然存在。 所以这是潜在的内存泄漏,这就是R#警告的原因。

@splintor和C#一样,匿名方法总是存储在一个类中,有两种方法可以避免这种情况:

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

  2. 将lambdaexpression式的创build拆分为两个方法。

同意Peter Mortensen。

C#编译器只生成一个types,封装方法中所有lambdaexpression式的所有variables。

例如,给定源代码:

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

编译器生成一个types如下所示:

 [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中使用的生成类的属性。

该警告是有效的,并显示在具有多个lambda的方法中 ,并捕获不同的值

当调用包含lambdaexpression式的方法时,编译器生成的对象将被实例化为:

  • 表示lambdaexpression式的实例方法
  • 代表任何这些lambdaexpression式所有值的字段

举个例子:

 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的实例创build了商店p1p2

想象一下:

  • callable1保持对其参数helper.Lambda1的长期参考
  • callable2不保留对其参数helper.Lambda2

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

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