Esta inspección llama su atención sobre el hecho de que se están capturando más valores de cierre de lo que obviamente es visible, lo que tiene un impacto en la vida útil de estos valores.
Considere el siguiente 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);
};
}
}
En el primer cierre, vemos que tanto obj1 como obj2 están siendo capturados explícitamente; podemos ver esto solo mirando el código. Para el segundo cierre, podemos ver que obj1 se está capturando explícitamente, pero ReSharper nos advierte que obj2 se está capturando implícitamente.
Esto se debe a un detalle de implementación en el compilador de C #. Durante la compilación, los cierres se reescriben en clases con campos que contienen los valores capturados y métodos que representan el cierre en sí. El compilador de C # solo creará una clase privada de este tipo por método, y si se define más de un cierre en un método, esta clase contendrá múltiples métodos, uno para cada cierre, y también incluirá todos los valores capturados de todos los cierres.
Si miramos el código que genera el compilador, se parece un poco a esto (algunos nombres se han limpiado para facilitar la lectura):
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);
}
}
Cuando se ejecuta el método, crea la clase de visualización, que captura todos los valores, para todos los cierres. Entonces, incluso si un valor no se usa en uno de los cierres, aún se capturará. Esta es la captura "implícita" que ReSharper está destacando.
La implicación de esta inspección es que el valor de cierre capturado implícitamente no se recolectará basura hasta que el cierre en sí mismo se recolecte. La vida útil de este valor ahora está vinculada a la vida útil de un cierre que no utiliza explícitamente el valor. Si el cierre es de larga duración, esto podría tener un efecto negativo en su código, especialmente si el valor capturado es muy grande.
Tenga en cuenta que si bien este es un detalle de implementación del compilador, es coherente en todas las versiones e implementaciones como Microsoft (antes y después de Roslyn) o el compilador de Mono. La implementación debe funcionar como se describe para manejar correctamente los cierres múltiples que capturan un tipo de valor. Por ejemplo, si varios cierres capturan un int, deben capturar la misma instancia, lo que solo puede suceder con una sola clase privada compartida anidada. El efecto secundario de esto es que la vida útil de todos los valores capturados es ahora la vida útil máxima de cualquier cierre que capture cualquiera de los valores.