Cette inspection attire votre attention sur le fait que plus de valeurs de fermeture sont capturées que ce n'est visiblement, ce qui a un impact sur la durée de vie de ces valeurs.
Considérez le code suivant:
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);
};
}
}
Dans la première fermeture, nous voyons que obj1 et obj2 sont explicitement capturés; nous pouvons le voir simplement en regardant le code. Pour la deuxième fermeture, nous pouvons voir que obj1 est explicitement capturé, mais ReSharper nous avertit que obj2 est implicitement capturé.
Cela est dû à un détail d'implémentation dans le compilateur C #. Pendant la compilation, les fermetures sont réécrites en classes avec des champs qui contiennent les valeurs capturées et des méthodes qui représentent la fermeture elle-même. Le compilateur C # ne créera qu'une seule classe privée par méthode, et si plus d'une fermeture est définie dans une méthode, cette classe contiendra plusieurs méthodes, une pour chaque fermeture, et elle inclura également toutes les valeurs capturées de toutes les fermetures.
Si nous regardons le code que le compilateur génère, il ressemble un peu à ceci (certains noms ont été nettoyés pour faciliter la lecture):
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);
}
}
Lorsque la méthode s'exécute, elle crée la classe d'affichage, qui capture toutes les valeurs, pour toutes les fermetures. Ainsi, même si une valeur n'est pas utilisée dans l'une des fermetures, elle sera toujours capturée. Il s'agit de la capture "implicite" que ReSharper met en évidence.
L'implication de cette inspection est que la valeur de fermeture implicitement capturée ne sera pas récupérée jusqu'à ce que la fermeture elle-même soit récupérée. La durée de vie de cette valeur est désormais liée à la durée de vie d'une fermeture qui n'utilise pas explicitement la valeur. Si la fermeture dure longtemps, cela pourrait avoir un effet négatif sur votre code, surtout si la valeur capturée est très grande.
Notez que bien qu'il s'agisse d'un détail d'implémentation du compilateur, il est cohérent entre les versions et implémentations telles que Microsoft (avant et après Roslyn) ou le compilateur de Mono. L'implémentation doit fonctionner comme décrit afin de gérer correctement plusieurs fermetures capturant un type de valeur. Par exemple, si plusieurs fermetures capturent un int, elles doivent capturer la même instance, ce qui ne peut se produire qu'avec une seule classe imbriquée privée partagée. L'effet secondaire de cela est que la durée de vie de toutes les valeurs capturées est désormais la durée de vie maximale de toute fermeture qui capture l'une des valeurs.