AKTUALIZACJA
Od C # 6 odpowiedź na to pytanie brzmi:
SomeEvent?.Invoke(this, e);
Często słyszę / czytam następujące porady:
Zawsze wykonaj kopię wydarzenia przed sprawdzeniem null
i odpaleniem. Eliminuje to potencjalny problem z wątkami, w których zdarzenie staje się null
w miejscu dokładnie między miejscem, w którym sprawdza się wartość zerową, a miejscem, w którym wydarzenie jest uruchamiane:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Zaktualizowany : z lektury na temat optymalizacji pomyślałem, że może to również wymagać zmienności członka zdarzenia, ale Jon Skeet stwierdza w swojej odpowiedzi, że CLR nie optymalizuje kopii.
Tymczasem, aby ten problem mógł wystąpić, inny wątek musiał zrobić coś takiego:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
Rzeczywistą sekwencją może być ta mieszanina:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Chodzi o to, że OnTheEvent
biegnie po tym, jak autor wypisał się z subskrypcji, a jednak po prostu wypisali się specjalnie, aby tego uniknąć. Z pewnością naprawdę potrzebna jest niestandardowa implementacja zdarzenia z odpowiednią synchronizacją w add
i remove
akcesoriach. Ponadto istnieje problem możliwego zakleszczenia, jeśli blokada zostanie przytrzymana podczas odpalenia zdarzenia.
Więc to jest programowanie Cargo Cult ? Wydaje się, że w ten sposób - wiele osób musi podjąć ten krok, aby chronić swój kod przed wieloma wątkami, podczas gdy w rzeczywistości wydaje mi się, że zdarzenia wymagają znacznie większej uwagi niż to, zanim będą mogły być użyte jako część wielowątkowego projektu . W związku z tym ludzie, którzy nie dbają o to, mogą równie dobrze zignorować tę radę - to po prostu nie jest problem w przypadku programów jednowątkowych, aw rzeczywistości, biorąc pod uwagę brak volatile
większości przykładowego kodu online, porady mogą nie zawierać efekt w ogóle.
(I czy nie jest o wiele prostsze przypisanie pustego delegate { }
miejsca do deklaracji członka, abyś nigdy nie musiał sprawdzać null
w pierwszej kolejności?)
Zaktualizowano:W przypadku, gdy nie było to jasne, zrozumiałem zamiar porady - aby uniknąć wyjątku zerowego odniesienia we wszystkich okolicznościach. Chodzi mi o to, że ten szczególny wyjątek odniesienia zerowego może wystąpić tylko wtedy, gdy inny wątek jest usuwany ze zdarzenia, a jedynym powodem tego jest zapewnienie, że żadne inne wywołania nie będą odbierane przez to zdarzenie, co wyraźnie nie jest osiągnięte tą techniką . Ukrywałbyś warunki wyścigu - lepiej byłoby je ujawnić! Ten wyjątek zerowy pomaga wykryć nadużycie komponentu. Jeśli chcesz, aby Twój komponent był chroniony przed nadużyciami, możesz pójść za przykładem WPF - zapisz identyfikator wątku w swoim konstruktorze, a następnie wyrzuć wyjątek, jeśli inny wątek spróbuje wejść w interakcję bezpośrednio z twoim komponentem. Albo zaimplementuj prawdziwie bezpieczny wątek (niełatwe zadanie).
Twierdzę więc, że samo wykonywanie tego idiomu kopiowania / sprawdzania jest programowaniem kultu, dodając bałagan i szum do twojego kodu. Rzeczywista ochrona przed innymi wątkami wymaga dużo więcej pracy.
Aktualizacja w odpowiedzi na posty na blogu Erica Lipperta:
Tak więc w modułach obsługi zdarzeń brakuje jednej ważnej rzeczy: „procedury obsługi zdarzeń muszą być niezawodne w obliczu bycia wywoływanym nawet po anulowaniu subskrypcji zdarzenia”, i oczywiście musimy tylko dbać o możliwość zdarzenia być delegatem null
. Czy ten wymóg dotyczący procedur obsługi zdarzeń jest gdziekolwiek udokumentowany?
I tak: „Istnieją inne sposoby rozwiązania tego problemu; na przykład inicjowanie modułu obsługi w celu wykonania pustej akcji, która nigdy nie jest usuwana. Ale sprawdzanie wartości zerowej jest standardowym wzorcem”.
Pozostaje więc fragment mojego pytania: dlaczego jawnie zeruje „standardowy wzorzec”? Alternatywą jest przypisanie pustego delegata, wymaga jedynie = delegate {}
dodania do deklaracji wydarzenia, a to eliminuje te małe stosy śmierdzącej ceremonii z każdego miejsca, w którym odbywa się wydarzenie. Łatwo byłoby upewnić się, że pusty delegat jest tani do utworzenia. A może wciąż czegoś brakuje?
Z pewnością musi tak być (jak sugerował Jon Skeet), to tylko rada .NET 1.x, która nie wygasła, tak jak powinna była to zrobić w 2005 roku?
EventName(arguments)
bezwarunkowo wywołać delegata zdarzenia, zamiast zmuszać go do wywoływania delegata tylko w przypadku wartości innej niż null (nie rób nic, jeśli wartość jest pusta).