2008-10-23 12 views
48

Wszyscy znamy przerażenie, które jest deklaracją zdarzenia C#. Aby zapewnić bezpieczeństwo, nitki the standard is to write something like this:Podnoszenie zdarzeń C# za pomocą metody rozszerzenia - czy to źle?

public event EventHandler SomethingHappened; 
protected virtual void OnSomethingHappened(EventArgs e) 
{    
    var handler = SomethingHappened; 
    if (handler != null) 
     handler(this, e); 
} 

Ostatnio w jakimś innym pytaniem na tej płycie (które nie mogę znaleźć teraz), ktoś zwrócił uwagę, że rozszerzenie metody mogą być wykorzystane ładnie w tym scenariuszu. Oto jeden ze sposobów, aby to zrobić:

static public class EventExtensions 
{ 
    static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e) 
    { 
     var handler = @event; 
     if (handler != null) 
      handler(sender, e); 
    } 
    static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e) 
     where T : EventArgs 
    { 
     var handler = @event; 
     if (handler != null) 
      handler(sender, e); 
    } 
} 

Z tych metod rozszerzenie na swoim miejscu, wszystko trzeba zadeklarować i podnieść zdarzenie jest mniej więcej tak:

public event EventHandler SomethingHappened; 

void SomeMethod() 
{ 
    this.SomethingHappened.RaiseEvent(this, EventArgs.Empty); 
} 

Moje pytanie: Czy to dobry pomysł ? Czy brakuje nam czegoś, nie mając standardowej metody On? (Jedną z rzeczy, które zauważam jest to, że nie działa z wydarzeniami, które mają wyraźny kod dodawania/usuwania).

+0

Może myślisz o tym pytaniu: http://stackoverflow.com/questions/192980/boiler-plate-code-replacement-is-there-nething-bad-about-to-code – Benjol

Odpowiedz

55

Będzie nadal działać z wydarzeniami, które mają wyraźne dodanie/usunięcie - wystarczy użyć zmiennej delegowanej (lub jakkolwiek zapisałeś delegata) zamiast nazwy wydarzenia.

Istnieje jednak prostszy sposób, aby to bezpieczny wątku - zainicjować go z uchwytu no-op:

public event EventHandler SomethingHappened = delegate {}; 

Hitem wydajność wywoływania dodatkowego delegata będzie znikome, a to na pewno sprawia, że kod łatwiejszy.

Nawiasem mówiąc, w swoim sposobie przedłużenia nie potrzeba dodatkowej zmiennej lokalnej - można po prostu zrobić:

static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e) 
{ 
    if (@event != null) 
     @event(sender, e); 
} 

static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e) 
    where T : EventArgs 
{ 
    if (@event != null) 
     @event(sender, e); 
} 

Osobiście nie użyłby słowa kluczowego jako nazwa parametru, ale to nie robi Naprawdę zmienić stronę wzywającą, więc rób to, co chcesz :)

EDYCJA: Co do metody "OnXXX": czy planujesz na podstawie swoich klas? Moim zdaniem większość klas należy zaplombować. Jeśli masz do, czy chcesz, aby te klasy pochodne były w stanie podnieść wydarzenie? Jeśli odpowiedź na którekolwiek z tych pytań brzmi "nie", nie przejmuj się. Jeśli odpowiedź na oba pytania brzmi "tak", to rób :)

+1

Dobra rada; @event nie może się zmienić, gdy jesteś w metodzie. Wiedziałem, że możesz zasubskrybować pustego delegata, ale moja troska dotyczyła raczej tego, czy wypada rezygnować z metody On. –

+0

Rozpoznaję tę sugestię, aby użyć delegata {} z Twojej doskonałej książki! To jest niesamowite :) –

+0

Pamiętaj tylko, jeśli twoja klasa ustawia delegata na wartość null, nadal będziesz mieć błąd. Naprawdę nie rozumiem, dlaczego tak wielu ludzi ma z tym problem. –

3

Mniej kodu, bardziej czytelny. Ja lubię.

Jeśli nie jesteś zainteresowany w wydajności można zadeklarować swoją imprezę tak, aby uniknąć czek zerowej:

public event EventHandler SomethingHappened = delegate{}; 
1

nie jesteś „zapewnienie” bezpieczeństwo wątek poprzez przypisanie do obsługi lokalnego zmienna. Twoja metoda może zostać przerwana po przydzieleniu zadania. Jeśli na przykład klasa, która służy do słuchania zdarzenia zostanie usunięta podczas przerwy, wywołujesz metodę w unieszkodliwionej klasie.

Zapisujesz się od wyjątku odwołującego się do wartości zerowej, ale są łatwiejsze sposoby na to, co zauważyli Jon Skeet i cristianlibardo w swoich odpowiedziach.

Inną sprawą jest to, że w przypadku klas niezamkniętych metoda OnFoo powinna być wirtualna, co nie wydaje mi się możliwe w przypadku metod rozszerzeń.

+0

Myślę, że chcieli powiedzieć "unikanie wyścigu". –

5

[Oto myśl]

Wystarczy napisać kod raz w zalecany sposób i być z nim zrobić. Wtedy nie pomylisz swoich kolegów patrzących na kod myśląc, że zrobiłeś coś złego?

[czytam więcej postów próbuje znaleźć sposoby obejścia pisania programu obsługi zdarzeń niż kiedykolwiek spędzają pisania programu obsługi zdarzeń.]

6

Teraz C# 6 jest tutaj, jest bardziej zwarta, wątku bezpieczny sposób na ogień wydarzenie:

SomethingHappened?.Invoke(this, e); 

Invoke() nazywa się tylko wtedy, gdy delegaci są rejestrowane dla zdarzenia (czyli nie jest null), dzięki operatora zerowej-warunkowe, „?”.

Problem z wątkiem, w którym znajduje się kod "obsługi" w pytaniu do rozwiązania, jest omijany tutaj, ponieważ podobnie jak w tym kodzie, SomethingHappened jest dostępny tylko raz, więc nie ma możliwości ustawienia go na wartość null między testem a wywołaniem .

Ta odpowiedź jest być może styczna do pierwotnego pytania, ale jest bardzo przydatna dla osób poszukujących prostszej metody podnoszenia zdarzeń.

Powiązane problemy