Pourquoi ReSharper me dit-il «fermeture implicitement saisie»?

J’ai le code suivant:

public double CalculateDailyProjectPullForceMax(DateTime date, ssortingng start = null, ssortingng 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 que ReSharper suggère un changement. Qu’est-ce que cela signifie, ou pourquoi devrait-il être changé? implicitly captured closure: end, start

L’avertissement vous indique que les variables se end et start restr en vie, car les lambda de cette méthode restnt actives.

Regardez le petit 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++.ToSsortingng(); this.button2.Click += (sender, args) => this.label1.Text = (g.Next() + i).ToSsortingng(); } 

Je reçois un avertissement “Fermeture implicite: g” au premier lambda. Il me dit que g ne peut pas être récupéré tant que le premier lambda est utilisé.

Le compilateur génère une classe pour les deux expressions lambda et met toutes les variables de cette classe qui sont utilisées dans les expressions lambda.

Donc, dans mon exemple, g et moi sums détenus dans la même classe pour l’exécution de mes delegates. Si g est un object lourd avec beaucoup de ressources, le ramasse-miettes ne peut pas le récupérer, car la référence dans cette classe est toujours active tant qu’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 existe deux manières d’éviter cela:

  1. Utilisez une méthode d’instance au lieu d’une méthode anonyme.

  2. Divisez la création des expressions lambda en deux méthodes.

Convenu avec Peter Mortensen.

Le compilateur C # génère un seul type qui encapsule toutes les variables pour toutes les expressions lambda dans 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 f = () => store.GetValue(); //Implicitly capture closure: x } } 

Le compilateur génère un type qui ressemble à:

 [ComstackrGenerated] 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 en tant que:

 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 func = new Func((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 des méthodes qui ont plus d’un lambda et qui capturent des valeurs différentes .

Lorsqu’une méthode contenant lambdas est appelée, un object généré par le compilateur est instancié avec:

  • méthodes d’instance représentant les lambdas
  • champs représentant toutes les valeurs capturées par l’ un de ces lambdas

Par exemple:

 class DecomstackMe { DecomstackMe(Action callable1, Action callable2) { var p1 = 1; var p2 = "hello"; callable1(() => p1++); // WARNING: Implicitly captured closure: p2 callable2(() => { p2.ToSsortingng(); p1++; }); } } 

Examinez le code généré pour cette classe (un peu rangé):

 class DecomstackMe { DecomstackMe(Action callable1, Action callable2) { var helper = new LambdaHelper(); helper.p1 = 1; helper.p2 = "hello"; callable1(helper.Lambda1); callable2(helper.Lambda2); } [ComstackrGenerated] private sealed class LambdaHelper { public int p1; public ssortingng p2; public void Lambda1() { ++p1; } public void Lambda2() { p2.ToSsortingng(); ++p1; } } } 

Notez que l’instance de LambdaHelper créée stocke à la fois 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 également référence indirectement à la chaîne dans p2 , ce qui signifie que le ramasse-miettes ne pourra pas le désallouer. Au pire, c’est une fuite de mémoire / de ressources. Sinon, il peut garder les objects en vie plus longtemps que nécessaire, ce qui peut avoir un impact sur GC s’ils sont promus de gen0 à gen1.

Pour les requêtes Linq to Sql, vous pouvez obtenir cet avertissement. La scope de lambda peut survivre à la méthode car la requête est souvent actualisée après que la méthode est hors de scope. Selon votre situation, vous pouvez actualiser les résultats (via .ToList ()) dans la méthode pour autoriser le GC sur les variables d’instance de la méthode capturées dans le lambda L2S.

Vous pouvez toujours trouver des raisons de suggestions R # en cliquant simplement sur les indications ci-dessous:

entrer la description de l'image ici

Cet indice vous dirigera ici .


Cette inspection attire votre attention sur le fait que plus de valeurs de fermeture sont capturées qu’il n’est de toute évidence, ce qui a un impact sur la durée de vie de ces valeurs.

Considérez le code suivant:

en utilisant le système; classe publique Class1 {privé 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 

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 contenant les valeurs capturées et les 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 plusieurs fermetures sont définies dans une méthode, cette classe contiendra plusieurs méthodes, une pour chaque fermeture, et toutes les valeurs capturées de toutes les fermetures.

Si nous regardons le code généré par le compilateur, cela ressemble à ceci (certains noms ont été nettoyés pour faciliter la lecture):

classe publique Class1 {[ComstackrGenerated] classe scellée privée <> c__DisplayClass1_0 {objet public obj1; objet public obj2;

  internal void b__0() { Console.WriteLine(obj1); Console.WriteLine(obj2); } internal void 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.b__0); _someAction += new Action(dc.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.

L’implication de cette inspection est que la valeur de fermeture implicitement capturée ne sera pas récupérée avant que la fermeture elle-même ne 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 peut 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 les 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 privée nestede partagée. L’effet secondaire 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.