2012-11-29 14 views
250

Mam następujący kod:Dlaczego ReSharper mówi mi "niejawnie przechwycone zamknięcie"?

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; 
} 

Teraz Dodałem komentarz na linii, która ReSharper jest sugerujących zmianę. Co to oznacza lub dlaczego musiałoby zostać zmienione? implicitly captured closure: end, start

+6

MyCodeSucks popraw poprawną odpowiedź: błąd kevingessner (jak wyjaśniono w komentarzach) i oznaczenie jako zaakceptowane wprowadzi użytkowników w błąd, jeśli nie zauważą odpowiedzi Konsoli. – Albireo

+0

Możesz również zobaczyć to, jeśli zdefiniujesz swoją listę poza próbą/catch i wykonasz całe swoje dodawanie w try/catch, a następnie ustawisz wyniki na inny obiekt. Przeniesienie definicji/dodanie w try/catch pozwoli GC. Mam nadzieję, że ma to sens. –

Odpowiedz

345

Ostrzeganie mówi, że zmienne pozostają żywe, ponieważ jakakolwiek z lambd w tej metodzie pozostaje przy życiu.

Spójrz na krótkim przykładzie

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(); 
} 

otrzymuję „Pośrednio przechwycony Zamknięcie: g” ostrzeżenie przy pierwszym lambda. Mówi mi, że g nie może być garbage collected tak długo, jak długo używana jest pierwsza lambda.

Kompilator generuje klasę dla obu wyrażeń lambda i umieszcza wszystkie zmienne w tej klasie, które są używane w wyrażeniach lambda.

Tak więc w moim przykładzie g i i są przechowywane w tej samej klasie w celu wykonania moich delegatów. Jeśli g jest ciężkim obiektem z dużą ilością zasobów, garbage collector nie może go odzyskać, ponieważ referencja w tej klasie jest wciąż żywa, dopóki używane jest jedno z wyrażeń lambda. Jest to potencjalny wyciek pamięci, i to jest powód ostrzeżenia R #.

@splintor jak w C# anonimowe metody są zawsze przechowywane w jednej klasie jednej metody istnieją dwa sposoby, aby tego uniknąć:

  1. użyć metody instancji zamiast anonimowego jeden.

  2. Podział kreacji wyrażeń lambda na dwie metody.

+20

Jakie są możliwe sposoby uniknięcia tego przechwytywania? – splintor

+1

Dzięki za tę wspaniałą odpowiedź - nauczyłem się, że istnieje powód, by używać metody nieanonimowej, nawet jeśli jest ona wykorzystywana w jednym miejscu. – ScottRhee

+1

@splintor Wywołaj obiekt wewnątrz delegata lub przekaż go jako parametr. W powyższym przypadku, o ile mogę powiedzieć, pożądanym zachowaniem jest utrzymywanie odniesienia do instancji 'Random'. – Casey

26

Uzgodniono z Peterem Mortensenem.

Kompilator C# generuje tylko jeden typ, który zawiera wszystkie zmienne dla wszystkich wyrażeń lambda w metodzie.

Na przykład, biorąc pod uwagę kod źródłowy:

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 
    } 
} 

Kompilator generuje rodzaj wygląda następująco:

[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(); 
    } 
} 

A metoda Capture jest kompilowany jako:

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)); 
} 

Chociaż druga lambda nie używa x, nie może to być śmieciarz jako x jest skompilowany jako własność wygenerowanej klasy używanej w lambda.

1

Dla zapytań Linq do Sql, możesz otrzymać to ostrzeżenie. Zasięg lambda może przeżyć tę metodę z uwagi na fakt, że kwerenda jest często aktualizowana po tym, jak metoda jest poza zakresem. W zależności od sytuacji możesz chcieć zaktualizować wyniki (np. Za pośrednictwem.ToList()) w ramach metody zezwalającej na GC na instrumencie instancji metody przechwytywanym w L2S lambda.

21

Ostrzeżenie jest ważne i wyświetlane w metodach, które mają więcej niż jeden lambda i one przechwytywać różne wartości.

Gdy metoda zawierająca lambdy jest wywoływana, obiekt kompilator generowane jest tworzony z:

  • metod przykład reprezentujących lambdy
  • pola reprezentujące wszystkie wartości przechwycony przez każdej tych lambda

Jako przykład:

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++; }); 
    } 
} 

Zbadać wygenerowany kod dla tej klasy (uporządkowany trochę):

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; } 
    } 
} 

Uwaga wystąpienie LambdaHelper utworzonych sklepach obu p1 i p2.

Wyobraźmy sobie, że:

  • callable1 utrzymuje się długo żyjące odniesienie do jego argumentacji, helper.Lambda1
  • callable2 nie przechowują odniesienia do jej argumentacji, helper.Lambda2

W tej sytuacji, odniesienie do helper.Lambda1 również pośrednio odwołuje się do ciągu znaków w p2, a to oznacza, że ​​śmieciarz nie będzie w stanie zlokalizuj to. W najgorszym przypadku wyciek pamięci/zasobów. Ewentualnie może utrzymywać obiekty dłużej niż to konieczne, co może mieć wpływ na GC, jeśli zostaną one awansowane z gen0 na gen1.

Powiązane problemy