web-dev-qa-db-de.com

C # -Muster, um zu verhindern, dass ein Ereignishandler zweimal verknüpft wird

Duplikat von: So stellen Sie sicher, dass ein Ereignis nur einmal abonniert wird und Wurde bereits ein Ereignishandler hinzugefügt?

Ich habe einen Singleton, der einige Dienste bereitstellt, und meine Klassen binden einige Ereignisse ein. Manchmal binden sich Klassen zweimal an das Ereignis und werden dann zweimal aufgerufen. Ich suche nach einem klassischen Weg, um dies zu verhindern. Irgendwie muss ich nachsehen, ob ich mich schon für dieses Event entschieden habe ...

90
Ali Shafai

Implementieren Sie das Ereignis explizit und überprüfen Sie die Aufrufliste. Sie müssen auch auf null prüfen:

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;
    }
}

Unter Verwendung des obigen Codes wird ein Anrufer, der das Ereignis mehrmals abonniert, einfach ignoriert.

143

Wie wäre es, wenn Sie das Ereignis zuerst mit -= Entfernen, wenn es nicht gefunden wird, wird keine Ausnahme ausgelöst

/// -= 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);
165
PrimeTSS

Ich habe jede Lösung getestet 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 Verwendung von Linq erforderlich. Vor dem Kündigen eines Abonnements muss nicht auf null geprüft werden (Details siehe MS EventHandler). Sie müssen nicht daran denken, die Abmeldung überall vorzunehmen.

33
LoxLox

Sie sollten dies wirklich auf der Senkenebene und nicht auf der Quellenebene handhaben. Das heißt, schreiben Sie keine Event-Handler-Logik an der Event-Quelle vor - überlassen Sie dies den Handlern (den Senken) selbst.

Wer ist der Entwickler eines Dienstes, von dem Sie behaupten, dass sich Waschbecken nur einmal registrieren können? Was ist, wenn sie sich aus irgendeinem Grund zweimal registrieren möchten? Wenn Sie versuchen, Fehler in den Senken durch Ändern der Quelle zu beheben, ist dies erneut ein guter Grund, diese Probleme auf Senkenebene zu beheben.

Ich bin sicher, Sie haben Ihre Gründe. 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.

19
JP Alioto

Sie müssen das Hinzufügen und Entfernen von Accessoren für das Ereignis implementieren und dann die Zielliste des Delegaten überprüfen oder die Ziele in einer Liste speichern.

In der add-Methode können Sie die Delegate.GetInvocationList -Methode verwenden, um eine Liste der Ziele abzurufen, die dem Delegaten bereits hinzugefügt wurden.

Da Delegaten so definiert sind, dass sie gleich vergleichen, wenn sie mit derselben Methode für dasselbe Zielobjekt verknüpft sind, können Sie diese Liste wahrscheinlich durchgehen und vergleichen. Wenn Sie keine finden, die gleich ist, fügen Sie die neue hinzu.

Hier ist ein Beispielcode, der als Konsolenanwendung kompiliert wird:

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");
        }
    }
}

Ausgabe:

tc_Test called

(dh nur einmal)

Microsofts Reactive Extensions (Rx) Framework kann auch verwendet werden, um "nur einmal zu abonnieren".

Bei einem Mausereignis foo.Clicked können Sie wie folgt abonnieren und nur einen einzigen Aufruf 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 Funktion zum einmaligen Abonnieren bietet der RX-Ansatz die Möglichkeit, Ereignisse zusammenzustellen oder Ereignisse zu filtern. Es ist ziemlich geschickt.

Erstellen Sie eine Aktion anstelle eines Ereignisses. Ihre Klasse könnte so 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);
     }

}
2
Tono Nam

In Silverlight müssen Sie sagen: e.Handled = true; 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 mich an, wenn dies hilft.

0
Omzig

lassen Sie Ihr Singleton-Objekt überprüfen, wer benachrichtigt wird, und rufen Sie es nur einmal an, wenn es doppelt vorhanden ist. Alternativ kann die Anforderung eines Ereignisanhangs nach Möglichkeit abgelehnt werden.

0
Toby Allen