Inspekcja ta zwraca uwagę na fakt, że rejestrowanych jest więcej wartości zamknięcia, niż jest to oczywiście widoczne, co ma wpływ na czas życia tych wartości.
Rozważ następujący kod:
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);
};
}
}
W pierwszym zamknięciu widzimy, że zarówno obj1, jak i obj2 są jawnie rejestrowane; możemy to zobaczyć po prostu patrząc na kod. Po drugim zamknięciu możemy zobaczyć, że obj1 jest jawnie przechwytywany, ale ReSharper ostrzega nas, że obj2 jest przechwytywany w sposób dorozumiany.
Wynika to ze szczegółów implementacji w kompilatorze C #. Podczas kompilacji zamknięcia są przepisywane na klasy z polami, które przechowują przechwycone wartości, oraz metodami reprezentującymi samo zamknięcie. Kompilator C # utworzy tylko jedną taką klasę prywatną dla każdej metody, a jeśli w metodzie zdefiniowano więcej niż jedno zamknięcie, wówczas klasa ta będzie zawierać wiele metod, po jednej dla każdego zamknięcia, a także będzie zawierać wszystkie przechwycone wartości ze wszystkich zamknięć.
Jeśli spojrzymy na kod generowany przez kompilator, wygląda to trochę tak (niektóre nazwy zostały wyczyszczone, aby ułatwić czytanie):
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);
}
}
Po uruchomieniu metody tworzy klasę wyświetlania, która przechwytuje wszystkie wartości dla wszystkich zamknięć. Więc nawet jeśli wartość nie zostanie użyta w jednym z zamknięć, nadal zostanie przechwycona. Jest to „niejawne” zdjęcie, które ReSharper podkreśla.
Implikacja tej kontroli polega na tym, że niejawnie uchwycona wartość zamknięcia nie zostanie wyrzucona, dopóki samo zamknięcie nie zostanie wyrzucone. Czas życia tej wartości jest teraz powiązany z czasem życia zamknięcia, które nie używa wyraźnie tej wartości. Jeśli zamknięcie jest długotrwałe, może to mieć negatywny wpływ na kod, szczególnie jeśli przechwycona wartość jest bardzo duża.
Pamiętaj, że chociaż jest to szczegół implementacji kompilatora, jest on spójny we wszystkich wersjach i implementacjach, takich jak Microsoft (przed Roslyn i po nim) lub kompilator Mono. Implementacja musi działać zgodnie z opisem, aby poprawnie obsługiwać wiele zamknięć przechwytujących typ wartości. Na przykład, jeśli wiele zamknięć przechwytuje liczbę całkowitą, muszą przechwycić tę samą instancję, co może się zdarzyć tylko w przypadku jednej wspólnej prywatnej zagnieżdżonej klasy. Efektem ubocznym jest to, że czas życia wszystkich przechwyconych wartości jest teraz maksymalnym czasem życia każdego zamknięcia, które przechwytuje dowolne wartości.