Pytanie Twórz automatycznie puste programy obsługi zdarzenia C #


Nie można wystrzelić zdarzenia w języku C #, do którego nie są dołączone żadne procedury obsługi. Dlatego przed każdym wywołaniem należy sprawdzić, czy zdarzenie ma wartość zerową.

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

Chciałbym zachować mój kod tak czysty, jak to możliwe i pozbyć się tych kontroli zerowych. Nie sądzę, że wpłynie to na wydajność, przynajmniej nie w moim przypadku.

MyEvent( param1, param2 );

W tej chwili rozwiązuję to przez ręczne dodawanie pustego wbudowanego programu obsługi do każdego zdarzenia. Jest to podatne na błędy, ponieważ muszę o tym pamiętać, itp.

void Initialize() {
  MyEvent += new MyEvent( (p1,p2) => { } );
}

Czy istnieje sposób na generowanie pustych procedur obsługi wszystkich zdarzeń danej klasy automatycznie za pomocą odbicia i magii CLR?


65
2017-12-04 13:41


pochodzenie


sztuczka w zaakceptowanej odpowiedzi pozwoli uniknąć konieczności sprawdzania wartości zerowej, ale nie zapewni bezpieczeństwa wątku. Spójrz tutaj: stackoverflow.com/questions/1131184/...


Odpowiedzi:


Widziałem to w innym poście i bezwstydnie go ukradłem i użyłem go w znacznej części mojego kodu od tego czasu:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click = delegate {}; // add empty delegate!

//Let you do this:
public void DoSomething() {
    Click(this, "foo");
}

//Instead of this:
public void DoSomething() {
    if (Click != null) // Unnecessary!
        Click(this, "foo");
}

* Jeśli ktokolwiek zna pochodzenie tej techniki, opublikuj ją w komentarzach. Naprawdę wierzę, że źródło otrzymuje należny kredyt.

(Edytować: Mam to z tego postu Ukryte funkcje C #?)


138
2017-12-04 13:44



24 sekundy szybciej! - leppie
Dodaj pustego delegata, właśnie tam! To nawet lepsze, niż miałem nadzieję. Dzięki! Przeczytam teraz wpis "ukrytych funkcji". - Tomas Andrle
lol. Myślę, że wyciąłem około 300 linii z mojej bazy kodów! - Stimul8d
-1 z dwóch powodów: 1) technika ta wpływa zarówno na wydajność środowiska wykonawczego, jak i na narzut pamięci oraz 2) ta technika jest podatna na błędy, szczególnie w porównaniu do metody rozszerzenia opisanej poniżej. Samo odsłonięcie strony wywołania nie jest wystarczające do określenia poprawności tej metody, ale metoda rozszerzenia działa dla wszystkich zdarzeń niezależnie od tego, czy zdarzenie zostało zainicjowane z pustym uczestnikiem. - Sam Harwell
@Koray Naprawdę nie pamiętam 10 lat temu. Prawdopodobnie byłem sarkastyczny: D Oops, on odpowiedział tą samą odpowiedzią co ja 24 sekundy szybciej. Zobacz mój poniżej. - leppie


Notacja:

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

nie jest bezpieczna dla wątków. Powinieneś zrobić to w ten sposób:

EventHandler handler = this.MyEvent;
if ( null != handler ) { handler( param1, param2 ); }

Rozumiem, że to przeszkadza, więc możesz zrobić metodę pomocnika:

static void RaiseEvent( EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

a następnie zadzwoń:

RaiseEvent( MyEvent, param1, param2 );

Jeśli używasz C # 3.0, możesz zadeklarować metodę pomocniczą jako metodę rozszerzenia:

static void Raise( this EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

a następnie zadzwoń:

MyEvent.Raise( param1, param2 );

Możesz także utworzyć następne metody rozszerzenia / pomocnika dla innych programów obsługi zdarzeń. Na przykład:

static void Raise<TEventArgs>( this EventHandler<TEventArgs> handler,
    object sender, TEventArgs e ) where TEventArgs : EventArgs
{
    if ( null != handler ) { handler( sender, e ); }
}

58
2017-12-04 13:53



Korzystanie z metody rozszerzenia jest pięknym rozwiązaniem. Powstrzymuję się, gdy pojawia się pojęcie inicjowania pustego delegata. - Greg
Wow, myślałem, że = delegate {} był przydatny, gdy zobaczyłem go po raz pierwszy. To jest jednak +1 niesamowite. I tak oczywiste z perspektywy czasu, do cholery :) - shambulator
+1 dla metod rozszerzenia - Wahid Bitar


Możesz pisać jako:

MyEvent += delegate { };

Nie jestem pewien, co chcesz zrobić, jest poprawne.


6
2017-12-04 13:44



Naprawdę wierzę, że dodanie pustego delegata do KAŻDEJ imprezy jest właściwą drogą, jak tworzyć aplikacje. Ale uważam, że może być sytuacja, gdzie jest szybkie i łatwe rozwiązanie, jak sobie z tym poradzić. - TcKs


Nie potrzebujesz kilku metod rozszerzenia dla różnych programów obsługi zdarzeń, potrzebujesz tylko jednego:

public static class EventHandlerExtensions {
  public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs {
    if (handler != null) handler(sender, args);
  }
}

5
2018-03-31 15:11



Dziękuję Sandro! - vkelman


W C # 6.0 nie ma potrzeby, aby przejść do żadnej z tych długości, aby wykonać zerową kontrolę, dzięki warunkowego operatora zerowego ?.

Dokumenty wyjaśnij, że dzwonisz MyEvent?.Invoke(...) kopiuje zdarzenie do zmiennej tymczasowej, wykonuje sprawdzanie zerowe, a jeśli nie jest puste, wywołuje Invoke na kopii tymczasowej. Nie musi to być wątek bezpieczny pod każdym względem, ponieważ ktoś mógłby dodać nowe zdarzenie po skopiowaniu do zmiennej tymczasowej, która nie zostałaby wywołana. Gwarantuje, że nie zadzwonisz Invoke przy zerowej wartości.

W skrócie:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click;

public void DoSomething() {
    Click?.Invoke(this, "foo");
}

3
2018-06-09 08:19





Jest to zły pomysł, ponieważ kod, który zużywa zdarzenie ma teraz oczekiwanie, że obiekt ze zdarzeniem został domyślnie zakodowany za pomocą akcji. Jeśli twój kod nigdy nie zostanie użyty nigdzie indziej przez nikogo innego, to myślę, że możesz temu zapobiec.


2
2017-12-04 13:53



Zgadzam się, skomentowałem odpowiedź leppiego. +1 - TcKs


Deklaracje zdarzeń C # niestety zawierają wiele dobrze znanych problemów bezpieczeństwa i nieefektywności. Zaprojektowałem wiele metod rozszerzenia dla delegatów, aby wywoływać je bezpiecznie, oraz rejestrować / wyrejestrowywać delegatów w sposób bezpieczny dla wątków.

Twój stary kod zdarzeń:

if (someDelegate != null) someDelegate(x, y, z);

Twój nowy kod:

someDelegate.Raise(x, y, z);

Twój stary kod rejestracyjny zdarzenia:

event Action fooEvent;
...
lock (someDummyObject) fooEvent += newHandler;

Twój nowy kod:

Action fooEvent;
...
Events.Add(ref fooEvent, newHandler);

Nie jest wymagane blokowanie, nie są wstawiane obiekty obojętne z kompilatorem, używane do blokowania zdarzeń.


1
2018-01-28 02:00