2008-11-20 9 views
97

Jest to rozszerzenie pytania z Access to Modified Closure. Chcę tylko sprawdzić, czy poniższe elementy są wystarczająco bezpieczne do użytku produkcyjnego.Dostęp do zmodyfikowanego zamknięcia (2)

List<string> lists = new List<string>(); 
//Code to retrieve lists from DB  
foreach (string list in lists) 
{ 
    Button btn = new Button(); 
    btn.Click += new EventHandler(delegate { MessageBox.Show(list); }); 
} 

Wykonuję powyższe czynności tylko raz podczas uruchamiania. Na razie wygląda na to, że działa dobrze. Jak Jon wspomniał w niektórych przypadkach o nieintuicyjnym wyniku. Więc co muszę tu zwrócić? Czy będzie dobrze, jeśli lista jest uruchamiana więcej niż raz?

+16

Gratulacje, jesteś teraz częścią dokumentacji Resharper. http: //confluence.jetbrains.net/display/ReSharper/Access + to + modified + closure – Kongress

+1

Ten problem był trudny, ale powyższe wyjaśnienie wyjaśniło mi wyraźnie: _To może wydawać się poprawne, ale w rzeczywistości tylko ostatnia wartość zmiennej str będzie używane po każdym kliknięciu dowolnego przycisku. Powodem tego jest to, że foreach rozwija się do pętli while, ale zmienna iteracyjna jest zdefiniowana poza tą pętlą. Oznacza to, że do czasu wyświetlenia okna komunikatu wartość str mogła już być iterowana do ostatniej wartości w kolekcji ciągów._ – DanielV

Odpowiedz

154

Przed C# 5, trzeba ponownie zadeklarować zmienną wewnątrz foreach - w przeciwnym razie jest on dzielony, a wszystkie koparki użyje ostatni ciąg:

foreach (string list in lists) 
{ 
    string tmp = list; 
    Button btn = new Button(); 
    btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); }); 
} 

Znacznie pamiętać, że od C# 5 i to się zmieniło, i w szczególności w przypadku foreach, nie musisz już tego robić: kod w pytaniu będzie działał zgodnie z oczekiwaniami.

Do tego nie działa bez tej zmiany pokazują, należy rozważyć następujące kwestie:

string[] names = { "Fred", "Barney", "Betty", "Wilma" }; 
using (Form form = new Form()) 
{ 
    foreach (string name in names) 
    { 
     Button btn = new Button(); 
     btn.Text = name; 
     btn.Click += delegate 
     { 
      MessageBox.Show(form, name); 
     }; 
     btn.Dock = DockStyle.Top; 
     form.Controls.Add(btn); 
    } 
    Application.Run(form); 
} 

Run wyżej przed C# 5, i choć każdy przycisk pokazuje inną nazwę, klikając przyciski pokazuje „Wilma” cztery razy.

to dlatego, że specyfikacja języka (ECMA 334 V4, 15.8.4) (przed C# 5) określa:

foreach (V v in x)embedded-statement następnie rozszerzony:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
     V v; 
     while (e.MoveNext()) { 
      v = (V)(T)e.Current; 
      embedded-statement 
     } 
    } 
    finally { 
     … // Dispose e 
    } 
} 

Zauważ, że zmienna v (która jest twoją list) jest zadeklarowana jako poza pętli. Zgodnie z regułami przechwyconych zmiennych wszystkie iteracje na liście będą dzielić przechwycone zmienne.

Od C# 5 jest to zmienione: zmienna iteracji (v) ma zasięg wewnątrz pętli. Nie mam odniesienie specyfikacji, ale to w zasadzie staje:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
     while (e.MoveNext()) { 
      V v = (V)(T)e.Current; 
      embedded-statement 
     } 
    } 
    finally { 
     … // Dispose e 
    } 
} 

Re anulowanie subskrypcji; Jeśli aktywnie chcą wypisać anonimowy obsługi, sztuką jest, aby uchwycić obsługi samego:

EventHandler foo = delegate {...code...}; 
obj.SomeEvent += foo; 
... 
obj.SomeEvent -= foo; 

Podobnie, jeśli chcesz jednorazowe zdarzeń obsługi (takie jak obciążenie itp):

EventHandler bar = null; // necessary for "definite assignment" 
bar = delegate { 
    // ... code 
    obj.SomeEvent -= bar; 
}; 
obj.SomeEvent += bar; 

To jest teraz self-unsubscribing ;-p

+0

W takim przypadku zmienna tymczasowa pozostanie w pamięci do momentu zamknięcia aplikacji, ponieważ aby służyć delegatowi i nie będzie to wskazane w przypadku bardzo dużych pętli, jeśli zmienna zajmuje dużo pamięci. Czy mam rację? – faulty

+1

Pozostanie w pamięci przez czas, w którym są rzeczy (przyciski) ze zdarzeniem. Jest sposób na wyłączenie subskrybujących jednorazowych delegatów, które dodam do postu. –

+2

Ale aby zakwalifikować się na twój punkt: tak, przechwycone zmienne mogą rzeczywiście zwiększyć zakres zmiennej. Musisz uważać, aby nie uchwycić rzeczy, których się nie spodziewałeś ... –

Powiązane problemy