为什么ReSharper告诉我“隐式捕获关闭”?

本文翻译自:Why does ReSharper tell me “implicitly captured closure”?

I have the following code: 我有以下代码:

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

Now, I've added a comment on the line that ReSharper is suggesting a change. 现在,我在ReSharper建议改变的行上添加了评论。 What does it mean, or why would it need to be changed? 这是什么意思,或者为什么需要改变? implicitly captured closure: end, start


#1楼

参考:https://stackoom.com/question/VcJ3/为什么ReSharper告诉我-隐式捕获关闭


#2楼

The warning tells you that the variables end and start stay alive as any of the lambdas inside this method stay alive. 该警告告诉您变量endstart保持活动状态,因为此方法中的任何lambda都保持活动状态。

Take a look at the short example 看看简短的例子

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

I get an "Implicitly captured closure: g" warning at the first lambda. 我在第一个lambda上得到了一个“隐式捕获的闭包:g”警告。 It is telling me that g cannot be garbage collected as long as the first lambda is in use. 它告诉我,只要第一个lambda正在使用中, g就不能被垃圾收集

The compiler generates a class for both lambda expressions and puts all variables in that class which are used in the lambda expressions. 编译器为两个lambda表达式生成一个类,并将所有变量放在lambda表达式中使用的该类中。

So in my example g and i are held in the same class for execution of my delegates. 所以在我的例子中, gi被保存在同一个类中以执行我的代理。 If g is a heavy object with a lot of resources left behind, the garbage collector couldn't reclaim it, because the reference in this class is still alive as long as any of the lambda expressions is in use. 如果g是一个有大量资源的重型对象,垃圾收集器就无法回收它,因为只要正在使用任何lambda表达式,该类中的引用仍然存在。 So this is a potential memory leak, and that is the reason for the R# warning. 所以这是潜在的内存泄漏,这就是R#警告的原因。

@splintor As in C# the anonymous methods are always stored in one class per method there are two ways to avoid this: @splintor与在C#中一样,匿名方法总是存储在每个方法的一个类中,有两种方法可以避免这种情况:

  1. Use an instance method instead of an anonymous one. 使用实例方法而不是匿名方法。

  2. Split the creation of the lambda expressions into two methods. 将lambda表达式的创建拆分为两个方法。


#3楼

Agreed with Peter Mortensen. 同意Peter Mortensen。

The C# compiler generates only one type that encapsulates all variables for all lambda expressions in a method. C#编译器只生成一个类型,它封装了方法中所有lambda表达式的所有变量。

For example, given the source code: 例如,给定源代码:

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

The compiler generates a type looks like : 编译器生成的类型如下:

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

And the Capture method is compiled as: 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));
}

Though the second lambda does not use x , it cannot be garbage collected as x is compiled as a property of the generated class used in the lambda. 虽然第二个lambda不使用x ,但它不能被垃圾收集,因为x被编译为lambda中使用的生成类的属性。


#4楼

For Linq to Sql queries, you may get this warning. 对于Linq to Sql查询,您可能会收到此警告。 The lambda's scope may outlive the method due to the fact that the query is often actualized after the method is out of scope. 由于查询通常在方法超出范围后实现,因此lambda的范围可能比方法更长。 Depending on your situation, you may want to actualize the results (ie via .ToList()) within the method to allow for GC on the method's instance vars captured in the L2S lambda. 根据您的具体情况,您可能希望在方法中实现结果(即通过.ToList()),以允许在L2S lambda中捕获的方法实例变量上使用GC。


#5楼

The warning is valid and displayed in methods that have more than one lambda , and they capture different values . 警告有效并显示在具有多个lambda的方法中 ,并且它们捕获不同的值

When a method that contains lambdas is invoked, a compiler-generated object is instantiated with: 当调用包含lambdas的方法时,将使用以下代码实例化编译器生成的对象:

  • instance methods representing the lambdas 表示lambda的实例方法
  • fields representing all values captured by any of those lambdas 表示由任何 lambda捕获的所有值的字段

As an example: 举个例子:

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

Examine the generated code for this class (tidied up a little): 检查这个类的生成代码(整理一下):

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

Note the instance of LambdaHelper created stores both p1 and p2 . 请注意, LambdaHelper创建的实例存储了p1p2

Imagine that: 设想:

  • callable1 keeps a long-lived reference to its argument, helper.Lambda1 callable1保持对其参数helper.Lambda1的长期引用
  • callable2 does not keep a reference to its argument, helper.Lambda2 callable2不会保持对其参数helper.Lambda2

In this situation, the reference to helper.Lambda1 also indirectly references the string in p2 , and this means that the garbage collector will not be able to deallocate it. 在这种情况下,对helper.Lambda1的引用也间接引用了p2的字符串,这意味着垃圾收集器将无法解除分配它。 At worst it is a memory/resource leak. 在最坏的情况下,它是内存/资源泄漏。 Alternatively it may keep object(s) alive longer than otherwise needed, which can have an impact on GC if they get promoted from gen0 to gen1. 或者,它可以使对象保持比其他需要更长的时间,如果它们从gen0升级到gen1,则可能对GC产生影响。


#6楼

You could always figure out with a reasons of R# suggestions just by clicking on the hints like shown below: 你总是可以通过单击如下所示的提示找出R#建议的原因:

在此输入图像描述

This hint will direct you here . 这个提示会引导你到这里来


This inspection draws your attention to the fact that more closure values are being captured than is obviously visibly, which has an impact on the lifetime of these values. 这种检查使您注意到这样一个事实,即捕获的闭合值明显高于明显可见的闭合值,这会对这些值的寿命产生影响。

Consider the following code: 请考虑以下代码:

using System; 使用系统; public class Class1 { private Action _someAction; public class Class1 {private Action _someAction;

 public void Method() { var obj1 = new object(); var obj2 = new object(); _someAction += () => { Console.WriteLine(obj1); Console.WriteLine(obj2); }; // "Implicitly captured closure: obj2" _someAction += () => { Console.WriteLine(obj1); }; } } In the first closure, we see that both obj1 and obj2 are being explicitly captured; we can see this just by looking at the code. For 

the second closure, we can see that obj1 is being explicitly captured, but ReSharper is warning us that obj2 is being implicitly captured. 第二个闭包,我们可以看到obj1被明确捕获,但是ReSharper警告我们obj2被隐式捕获。

This is due to an implementation detail in the C# compiler. 这是由于C#编译器中的实现细节。 During compilation, closures are rewritten into classes with fields that hold the captured values, and methods that represent the closure itself. 在编译期间,闭包被重写为具有保存捕获值的字段的类,以及表示闭包本身的方法。 The C# compiler will only create one such private class per method, and if more than one closure is defined in a method, then this class will contain multiple methods, one for each closure, and it will also include all captured values from all closures. C#编译器每个方法只会创建一个这样的私有类,如果在方法中定义了多个闭包,那么这个类将包含多个方法,每个闭包一个,并且它还包括所有闭包的所有捕获值。

If we look at the code that the compiler generates, it looks a little like this (some names have been cleaned up to ease reading): 如果我们查看编译器生成的代码,它看起来有点像这样(有些名称已被清理以方便阅读):

public class Class1 { [CompilerGenerated] private sealed class <>c__DisplayClass1_0 { public object obj1; public class Class1 {[CompilerGenerated] private sealed class <> c__DisplayClass1_0 {public object obj1; public object obj2; 公共对象obj2;

  internal void <Method>b__0() { Console.WriteLine(obj1); Console.WriteLine(obj2); } internal void <Method>b__1() { Console.WriteLine(obj1); } } private Action _someAction; public void Method() { // Create the display class - just one class for both closures var dc = new Class1.<>c__DisplayClass1_0(); // Capture the closure values as fields on the display class dc.obj1 = new object(); dc.obj2 = new object(); // Add the display class methods as closure values _someAction += new Action(dc.<Method>b__0); _someAction += new Action(dc.<Method>b__1); } } When the method runs, it creates the display class, which captures all values, for all closures. So even if a value isn't used 

in one of the closures, it will still be captured. 在其中一个闭包中,它仍将被捕获。 This is the "implicit" capture that ReSharper is highlighting. 这是ReSharper强调的“隐式”捕获。

The implication of this inspection is that the implicitly captured closure value will not be garbage collected until the closure itself is garbage collected. 这种检查的含义是,在关闭本身被垃圾收集之前,隐式捕获的闭包值不会被垃圾收集。 The lifetime of this value is now tied to the lifetime of a closure that does not explicitly use the value. 此值的生命周期现在与未明确使用该值的闭包的生命周期相关联。 If the closure is long lived, this might have a negative effect on your code, especially if the captured value is very large. 如果闭包是长寿的,这可能会对您的代码产生负面影响,特别是如果捕获的值非常大。

Note that while this is an implementation detail of the compiler, it is consistent across versions and implementations such as Microsoft (pre and post Roslyn) or Mono's compiler. 请注意,虽然这是编译器的实现细节,但它在Microsoft(Roslyn之前和之后)或Mono编译器等版本和实现中是一致的。 The implementation must work as described in order to correctly handle multiple closures capturing a value type. 实现必须按照描述的方式工作,以便正确处理捕获值类型的多个闭包。 For example, if multiple closures capture an int, then they must capture the same instance, which can only happen with a single shared private nested class. 例如,如果多个闭包捕获一个int,那么它们必须捕获相同的实例,这只能在一个共享的私有嵌套类中发生。 The side effect of this is that the lifetime of all captured values is now the maximum lifetime of any closure that captures any of the values. 这样做的副作用是所有捕获值的生命周期现在是捕获任何值的任何闭包的最大生命周期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值