Essa inspeção chama a atenção para o fato de que mais valores de fechamento estão sendo capturados do que é obviamente visível, o que afeta a vida útil desses valores.
Considere o seguinte código:
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);
};
}
}
No primeiro fechamento, vemos que obj1 e obj2 estão sendo explicitamente capturados; podemos ver isso apenas olhando o código. Para o segundo fechamento, podemos ver que o obj1 está sendo capturado explicitamente, mas o ReSharper está nos alertando que o obj2 está sendo capturado implicitamente.
Isso ocorre devido a um detalhe de implementação no compilador C #. Durante a compilação, os fechamentos são reescritos em classes com campos que mantêm os valores capturados e métodos que representam o próprio fechamento. O compilador C # criará apenas uma classe privada por método e, se mais de um fechamento for definido em um método, essa classe conterá vários métodos, um para cada fechamento, e também incluirá todos os valores capturados de todos os fechamentos.
Se observarmos o código que o compilador gera, ele se parece um pouco com isso (alguns nomes foram limpos para facilitar a leitura):
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);
}
}
Quando o método é executado, ele cria a classe de exibição, que captura todos os valores, para todos os fechamentos. Portanto, mesmo que um valor não seja usado em um dos fechamentos, ele ainda será capturado. Esta é a captura "implícita" que o ReSharper está destacando.
A implicação dessa inspeção é que o valor do fechamento capturado implicitamente não será coletado como lixo até que o próprio fechamento seja coletado. O tempo de vida desse valor agora está vinculado ao tempo de vida de um fechamento que não usa explicitamente o valor. Se o fechamento for prolongado, isso poderá ter um efeito negativo no seu código, especialmente se o valor capturado for muito grande.
Observe que, embora esse seja um detalhe de implementação do compilador, ele é consistente entre versões e implementações, como o Microsoft (antes e depois do Roslyn) ou o compilador Mono. A implementação deve funcionar como descrito para lidar corretamente com vários fechamentos que capturam um tipo de valor. Por exemplo, se vários fechamentos capturarem um int, eles deverão capturar a mesma instância, o que só pode acontecer com uma única classe aninhada privada compartilhada. O efeito colateral disso é que a vida útil de todos os valores capturados agora é a vida útil máxima de qualquer fechamento que captura qualquer um dos valores.