J'ai le code suivant:
public double CalculateDailyProjectPullForceMax(DateTime date, string start = null, string end = null)
{
Log("Calculating Daily Pull Force Max...");
var pullForceList = start == null
? _pullForce.Where((t, i) => _date[i] == date).ToList() // implicitly captured closure: end, start
: _pullForce.Where(
(t, i) => _date[i] == date && DateTime.Compare(_time[i], DateTime.Parse(start)) > 0 &&
DateTime.Compare(_time[i], DateTime.Parse(end)) < 0).ToList();
_pullForceDailyMax = Math.Round(pullForceList.Max(), 2, MidpointRounding.AwayFromZero);
return _pullForceDailyMax;
}
Maintenant, j'ai ajouté un commentaire sur la ligne qui ReSharper suggère un changement. Qu'est-ce que cela signifie ou pourquoi faudrait-il le changer? implicitly captured closure: end, start
L'avertissement vous indique que les variables end
et start
restent en vie, tout comme l'un des lambda de cette méthode le reste.
Regardez le court exemple
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
int i = 0;
Random g = new Random();
this.button1.Click += (sender, args) => this.label1.Text = i++.ToString();
this.button2.Click += (sender, args) => this.label1.Text = (g.Next() + i).ToString();
}
Je reçois un avertissement "Implicitement capturé: g" lors du premier lambda. Cela me dit que g
ne peut pas être ordures collectées tant que le premier lambda est utilisé.
Le compilateur génère une classe pour les deux expressions lambda et place toutes les variables de cette classe utilisées dans les expressions lambda.
Ainsi, dans mon exemple, g
et i
sont conservés dans la même classe pour l'exécution de mes délégués. Si g
est un objet lourd avec de nombreuses ressources, le récupérateur de mémoire ne peut pas le récupérer, car la référence de cette classe est toujours active tant que l'une des expressions lambda est utilisée. Il s’agit donc d’une fuite de mémoire potentielle et c’est la raison de l’avertissement R #.
@splintor Comme en C #, les méthodes anonymes sont toujours stockées dans une classe par méthode, il y a deux façons d'éviter cela:
Utilisez une méthode d'instance au lieu d'une méthode anonyme.
Divisez la création des expressions lambda en deux méthodes.
D'accord avec Peter Mortensen.
Le compilateur C # génère un seul type qui encapsule toutes les variables pour toutes les expressions lambda d'une méthode.
Par exemple, étant donné le code source:
public class ValueStore
{
public Object GetValue()
{
return 1;
}
public void SetValue(Object obj)
{
}
}
public class ImplicitCaptureClosure
{
public void Captured()
{
var x = new object();
ValueStore store = new ValueStore();
Action action = () => store.SetValue(x);
Func<Object> f = () => store.GetValue(); //Implicitly capture closure: x
}
}
Le compilateur génère un type ressemblant à:
[CompilerGenerated]
private sealed class c__DisplayClass2
{
public object x;
public ValueStore store;
public c__DisplayClass2()
{
base.ctor();
}
//Represents the first lambda expression: () => store.SetValue(x)
public void Capturedb__0()
{
this.store.SetValue(this.x);
}
//Represents the second lambda expression: () => store.GetValue()
public object Capturedb__1()
{
return this.store.GetValue();
}
}
Et la méthode Capture
est compilée comme suit:
public void Captured()
{
ImplicitCaptureClosure.c__DisplayClass2 cDisplayClass2 = new ImplicitCaptureClosure.c__DisplayClass2();
cDisplayClass2.x = new object();
cDisplayClass2.store = new ValueStore();
Action action = new Action((object) cDisplayClass2, __methodptr(Capturedb__0));
Func<object> func = new Func<object>((object) cDisplayClass2, __methodptr(Capturedb__1));
}
Bien que le second lambda n'utilise pas x
, il ne peut pas être récupéré, car x
est compilé en tant que propriété de la classe générée utilisée dans le lambda.
L'avertissement est valide et affiché dans les méthodes qui ont plusieurs lambda, et ils capturent des valeurs différentes.
Lorsqu'une méthode contenant lambdas est appelée, un objet généré par le compilateur est instancié avec:
Par exemple:
class DecompileMe
{
DecompileMe(Action<Action> callable1, Action<Action> callable2)
{
var p1 = 1;
var p2 = "hello";
callable1(() => p1++); // WARNING: Implicitly captured closure: p2
callable2(() => { p2.ToString(); p1++; });
}
}
Examinez le code généré pour cette classe (un peu rangé):
class DecompileMe
{
DecompileMe(Action<Action> callable1, Action<Action> callable2)
{
var helper = new LambdaHelper();
helper.p1 = 1;
helper.p2 = "hello";
callable1(helper.Lambda1);
callable2(helper.Lambda2);
}
[CompilerGenerated]
private sealed class LambdaHelper
{
public int p1;
public string p2;
public void Lambda1() { ++p1; }
public void Lambda2() { p2.ToString(); ++p1; }
}
}
Notez que l'instance de LambdaHelper
créée enregistre p1
et p2
.
Imagine ça:
callable1
conserve une référence de longue durée à son argument, helper.Lambda1
callable2
ne conserve pas de référence à son argument, helper.Lambda2
Dans cette situation, la référence à helper.Lambda1
fait également référence indirectement à la chaîne dans p2
, ce qui signifie que le garbage collector ne pourra pas le désallouer. Au pire c'est une fuite de mémoire/ressource. Sinon, les objets peuvent rester en vie plus longtemps que nécessaire, ce qui peut avoir un impact sur le GC s'ils sont promus de gen0 à gen1.
Pour les requêtes Linq to Sql, vous pouvez obtenir cet avertissement. La portée de lambda peut survivre à la méthode car la requête est souvent actualisée une fois que la méthode est hors de portée. En fonction de votre situation, vous souhaiterez peut-être actualiser les résultats (c.-à-d. Via .ToList ()) dans la méthode pour autoriser la GC sur les vars d'instance de la méthode capturés dans le L2S lambda.
Cet indice vous dirigera ici .
Cette inspection attire votre attention sur le fait que plus de valeurs de fermeture sont capturées que ce qui est manifestement visible, ce qui a un impact sur la durée de vie de ces valeurs.
Considérons le code suivant:
en utilisant le système; Classe publique 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); }; } } In the first closure, we see that both obj1 and obj2 are being explicitly captured; we can see this just by looking at the code. For
lors de la seconde 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 #. Lors de la compilation, les fermetures sont réécrites en classes avec des champs contenant les valeurs capturées et des méthodes représentant la fermeture elle-même. Le compilateur C # ne créera qu'une seule classe privée de ce type par méthode et si plusieurs fermetures sont définies dans une méthode, cette classe contiendra plusieurs méthodes, une pour chaque fermeture, et inclura toutes les valeurs capturées de toutes les fermetures.
Si nous regardons le code généré par le compilateur, il ressemble un peu à ceci (certains noms ont été nettoyés pour en faciliter la lecture):
classe publique Class1 {[CompilerGenerated] classe privée privée <> c__DisplayClass1_0 {objet public obj1; objet public 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); } } When the method runs, it creates the display class, which captures all values, for all closures. So even if a value isn't used
dans l'une des fermetures, il sera toujours capturé. C'est la capture "implicite" que ReSharper met en évidence.
Cette inspection implique que la valeur de fermeture capturée implicitement ne sera pas collectée jusqu'à ce que la fermeture elle-même soit collectée. La durée de vie de cette valeur est maintenant 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 qu'il s'agit d'un détail d'implémentation du compilateur, mais qu'il est cohérent entre les versions et les implémentations telles que Microsoft (pré et post 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 alors capturer la même instance, ce qui ne peut se produire qu'avec une seule classe imbriquée privée partagée partagée. L'effet secondaire de cela est que la durée de vie de toutes les valeurs capturées est maintenant la durée de vie maximale de toute fermeture capturant l'une quelconque des valeurs.