该检查使您注意以下事实:捕获的封闭值比明显可见的多,这对这些值的寿命有影响。
考虑以下代码:
using System;
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);
};
}
}
在第一个闭包中,我们看到obj1和obj2都被显式捕获。我们只要看一下代码就可以看到这一点。对于第二个关闭,我们可以看到obj1被显式捕获,但是ReSharper警告我们obj2被隐式捕获。
这是由于C#编译器中的实现细节。在编译期间,闭包被重写为具有保存捕获值的字段和表示闭包本身的方法的类。C#编译器将为每个方法仅创建一个这样的私有类,并且如果在一个方法中定义了多个闭包,则该类将包含多个方法,每个闭包都包含一个方法,并且还将包括所有闭包中捕获的所有值。
如果我们看一下编译器生成的代码,它看起来像这样(一些名称已被清理以方便阅读):
public class Class1 {
[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
public object obj1;
public object 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);
}
}
该方法运行时,它将创建显示类,该类捕获所有闭包的所有值。因此,即使其中一个闭包中未使用值,它也将被捕获。这是ReSharper突出显示的“隐式”捕获。
此检查的含义是,直到关闭本身就是垃圾收集之后,才会对垃圾隐式捕获的关闭值进行垃圾收集。现在,此值的生存期与未显式使用该值的闭包的生存期相关。如果闭包的寿命很长,那么这可能会对您的代码产生负面影响,尤其是在捕获的值非常大的情况下。
请注意,尽管这是编译器的实现细节,但在各个版本和实现(例如Microsoft(在Roslyn之前和之后)或Mono的编译器)之间是一致的。为了正确处理捕获值类型的多个闭包,实现必须按所述方式工作。例如,如果多个闭包捕获一个int,则它们必须捕获同一实例,这只能在单个共享私有嵌套类中发生。这样做的副作用是,所有捕获的值的生存期现在是捕获任何值的任何闭包的最大生存期。