Эта проверка привлекает ваше внимание к тому факту, что фиксируется больше значений замыкания, чем это явно видно, что влияет на время жизни этих значений.
Рассмотрим следующий код:
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, они должны захватывать один и тот же экземпляр, что может происходить только с одним общим закрытым вложенным классом. Побочным эффектом этого является то, что время жизни всех захваченных значений теперь является максимальным временем жизни любого замыкания, которое захватывает любое из значений.