2009-06-01 4 views
73

Duplizieren von: How to ensure an event is only subscribed to once und Has an event handler already been added?C# Muster einen Ereignishandler zu verhindern süchtig zweimal

Ich habe einen Singleton, dass einige Service und meine Klassen Haken in einige Ereignisse auf sie bietet, manchmal eine Klasse hakt zweimal auf das Ereignis und dann wird zweimal angerufen. Ich suche nach einem klassischen Weg, dies zu verhindern. irgendwie muss ich überprüfen, ob ich bereits an diesem Ereignis süchtig bin ...

Antwort

120

Implementieren Sie das Ereignis explizit und überprüfen Sie die Aufrufliste. Sie werden auch für null überprüfen müssen:

using System.Linq; // Required for the .Contains call below: 

... 

private EventHandler foo; 
public event EventHandler Foo 
{ 
    add 
    { 
     if (foo == null || !foo.GetInvocationList().Contains(value)) 
     { 
      foo += value; 
     } 
    } 
    remove 
    { 
     foo -= value; 
    } 
} 

den oben stehenden Code verwenden, wenn ein Anrufer auf die Veranstaltung mehrfach abonniert, es wird einfach ignoriert.

+13

Sie müssen das System.Linq mit verwenden. –

+0

Nur um Hermanns Kommentar zu klären; Sie müssen den Namespace 'System.Linq' hinzufügen, indem Sie 'using System.Linq' zu Ihrer Klasse oder dem aktuellen Namespace hinzufügen. –

+0

Faszinierend. LINQ ist mir noch neu genug, dass ich es nachschlagen muss und daran erinnert werden muss, dass es sich um eine sprachintegrierte Abfrage handelt ... und frage mich dann, was das mit EventHandlern und ihrer InvocationList zu tun hat? – fortboise

12

Sie müssen die hinzufügen und entfernen Zugriffsmethoden auf das Ereignis, und überprüfen Sie dann die Zielliste des Delegaten, oder speichern Sie die Ziele in einem Liste.

In der Methode add können Sie mit der Methode Delegate.GetInvocationList eine Liste der Ziele abrufen, die dem Delegaten bereits hinzugefügt wurden.

Da Delegaten für den Vergleich von "Gleich" definiert sind, wenn sie mit derselben Methode für dasselbe Zielobjekt verknüpft sind, können Sie diese Liste möglicherweise durchlaufen und vergleichen. Wenn Sie keine Gleichheit finden, fügen Sie die Neue hinzu .

Hier ist Beispielcode kompilieren als Konsolenanwendung:

using System; 
using System.Linq; 

namespace DemoApp 
{ 
    public class TestClass 
    { 
     private EventHandler _Test; 

     public event EventHandler Test 
     { 
      add 
      { 
       if (_Test == null || !_Test.GetInvocationList().Contains(value)) 
        _Test += value; 
      } 

      remove 
      { 
       _Test -= value; 
      } 
     } 

     public void OnTest() 
     { 
      if (_Test != null) 
       _Test(this, EventArgs.Empty); 
     } 
    } 

    class Program 
    { 
     static void Main() 
     { 
      TestClass tc = new TestClass(); 
      tc.Test += tc_Test; 
      tc.Test += tc_Test; 
      tc.OnTest(); 
      Console.In.ReadLine(); 
     } 

     static void tc_Test(object sender, EventArgs e) 
     { 
      Console.Out.WriteLine("tc_Test called"); 
     } 
    } 
} 

Ausgang:

tc_Test called 

(dh nur einmal.)

+0

Bitte ignorieren Sie meinen Kommentar, ich vergaß die Linq mit. –

+0

Sauberste Lösung (obwohl nicht die kürzeste). – Shimmy

0

haben Ihr Singleton-Objekt überprüfen es Liste, wer es mitteilt, und nur einmal anrufen, wenn dupliziert. Alternativ können Sie nach Möglichkeit die Ereignisanhangsanfrage ablehnen.

17

Sie das wirklich an der Spüle Ebene behandeln sollen und nicht die Quelle Ebene. Das heißt, schreiben Sie keine Event-Handler-Logik bei der Ereignisquelle vor - überlassen Sie das den Handlern (den Senken) selbst.

Als der Entwickler eines Dienstes, wer sind Sie zu sagen, dass Senken nur einmal registrieren können? Was, wenn sie sich aus irgendeinem Grund zweimal anmelden möchten? Und wenn Sie versuchen, Fehler in den Senken zu korrigieren, indem Sie die Quelle ändern, ist dies wieder ein guter Grund, diese Probleme auf Senkenebene zu beheben.

Ich bin sicher, dass Sie Ihre Gründe haben; Eine Ereignisquelle, für die doppelte Senken illegal sind, ist nicht unergründlich. Aber vielleicht sollten Sie eine alternative Architektur in Betracht ziehen, die die Semantik eines Ereignisses intakt lässt.

+0

Dies ist eine hervorragende technische Begründung für [diese Lösung] (http://stackoverflow.com/a/1104269/3367144), die das Problem auf der Seite der Ereignissenke (Teilnehmer/Verbraucher/Beobachter/Handler) anstelle der Quelle löst Seite. – kdbanman

140

Wie wäre es nur das Ereignis zuerst mit -= entfernen, wenn es nicht eine Ausnahme ausgelöst wird nicht

/// -= Removes the event if it has been already added, this prevents multiple firing of the event 
((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii); 
((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii); 
+0

Danke. Dies war im selben Szenario sehr praktisch (WebBrowser + HtmlElementEventHandler). Danke, dass Sie darauf hingewiesen haben – Odys

+0

Dies sollte die akzeptierte Antwort sein, da es einfach ist und keine benutzerdefinierte Implementierung erfordert. @LoxLox zeigt das gleiche Muster wie eine Implementierung. Ich habe nicht getestet, also nehme ich die Kommentare bei ihrem Wort. Sehr schön. – Rafe

+3

+1 Ich denke, es liegt an dem Programmierer, aber ich würde sagen, das ist das Beste (nicht das "Schönste", um den Endentwickler dazu zu zwingen), aber es ist nicht der Fehler des Ereignisgenerators, den der Abonnent nicht verhindern kann mehrere Subskriptionen, also, lassen Sie sie herausfinden, die Entfernung, etc ... außerdem, warum verhindern, dass jemand den gleichen Handler mehr als einmal abonnieren, wenn die wollen?) –

6

Microsofts gefunden wird Reactive Extensions (Rx) framework kann auch zu tun „nur einmal subscribe“ verwendet werden.

ein Mausereignis foo.Clicked gegeben, hier ist, wie nur einen einzigen Aufruf abonnieren und erhalten:

Observable.FromEvent<MouseEventArgs>(foo, "Clicked") 
    .Take(1) 
    .Subscribe(MyHandler); 

... 

private void MyHandler(IEvent<MouseEventArgs> eventInfo) 
{ 
    // This will be called just once! 
    var sender = eventInfo.Sender; 
    var args = eventInfo.EventArgs; 
} 

Neben der Bereitstellung von „subscribe einmal“ Funktionalität bietet der RX-Ansatz die Möglichkeit, Ereignisse zusammen zu komponieren oder Ereignisse filtern. Es ist ziemlich geschickt.

+0

Obwohl das technisch korrekt ist, beantwortet es die falsche Frage. – AlexFoxGill

0

In silverlight müssen Sie e.Handled = true sagen; im Ereigniscode.

void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
{ 
    e.Handled = true; //this fixes the double event fire problem. 
    string name = (e.OriginalSource as Image).Tag.ToString(); 
    DoSomething(name); 
} 

Bitte kreuzen Sie an, wenn dies hilft.

1

Erstellen Sie eine Aktion anstelle eines Ereignisses. Ihre Klasse kann wie folgt aussehen:

public class MyClass 
{ 
       // sender arguments  <-----  Use this action instead of an event 
    public Action<object, EventArgs> OnSomeEventOccured; 

    public void SomeMethod() 
    { 
      if(OnSomeEventOccured!=null) 
       OnSomeEventOccured(this, null); 
    } 

} 
20

ich jede Lösung getestet haben und die beste (unter Berücksichtigung der Leistung) ist:

private EventHandler _foo; 
public event EventHandler Foo { 

    add { 
     _foo -= value; 
     _foo += value; 
    } 
    remove { 
     _foo -= value; 
    } 
} 

Keine Linq erforderlich verwenden. Sie müssen vor dem Abbrechen eines Abonnements nicht nach Null suchen (weitere Informationen finden Sie unter MS EventHandler). Keine Notwendigkeit, sich daran zu erinnern, die Abmeldung überall zu tun.

Verwandte Themen