2008-12-15 11 views
35

Ich möchte sicherstellen, dass ich nur einmal in einer bestimmten Klasse ein Ereignis für eine Instanz abonniere.So stellen Sie sicher, dass ein Ereignis nur einmal abonniert wird

Zum Beispiel würde Ich mag Lage sein, Folgendes zu tun:

if (*not already subscribed*) 
{ 
    member.Event += new MemeberClass.Delegate(handler); 
} 

Wie würde ich mich über eine solche Wache zu implementieren?

Antwort

31

Wenn Sie über ein Ereignis in einer Klasse sprechen, für die Sie Zugriff auf die Quelle haben, können Sie den Guard in die Ereignisdefinition einfügen.

private bool _eventHasSubscribers = false; 
private EventHandler<MyDelegateType> _myEvent; 

public event EventHandler<MyDelegateType> MyEvent 
{ 
    add 
    { 
     if (_myEvent == null) 
     { 
     _myEvent += value; 
     } 
    } 
    remove 
    { 
     _myEvent -= value; 
    } 
} 

Das würde sicherstellen, dass nur ein Abonnent das Ereignis für diese Instanz der Klasse abonnieren kann, die das Ereignis bereitstellt.

EDIT siehe Kommentare darüber, warum der oben genannte Code eine schlechte Idee und nicht Thread sicher ist.

Wenn Ihr Problem darin besteht, dass eine einzelne Instanz des Clients mehr als einmal abonniert wird (und Sie mehrere Abonnenten benötigen), muss der Client-Code damit umgehen. So ersetzen

nicht bereits

mit einem Bool Mitglied der Client-Klasse gezeichnet, die festgelegt wird, wenn Sie für die Veranstaltung zum ersten Mal abonnieren.

Edit (nach akzeptiert): Basierend auf den Kommentar von @Glen T (der Einreicher der Frage) den Code für die akzeptierte Lösung, die er mit ging in der Client-Klasse:

if (alreadySubscribedFlag) 
{ 
    member.Event += new MemeberClass.Delegate(handler); 
} 

Wo alreadySubscribedFlag ist eine Membervariable in der Clientklasse, die die erste Subskription für das spezifische Ereignis verfolgt. Leute, die hier das erste Code-Snippet betrachten, beachten Sie bitte @ Runes Kommentar - es ist keine gute Idee, das Verhalten des Abonnierens eines Ereignisses auf nicht naheliegende Weise zu ändern.

EDIT 31/7/2009: Bitte beachten Sie die Kommentare von @Sam Saffron. Wie ich bereits gesagt habe und Sam stimmt zu, dass die erste hier vorgestellte Methode keine sinnvolle Möglichkeit ist, das Verhalten des Ereignis-Abonnements zu modifizieren. Die Konsumenten der Klasse müssen über ihre interne Implementierung Bescheid wissen, um ihr Verhalten zu verstehen. Nicht sehr nett.
@Sam Saffron kommentiert auch die Sicherheit von Fäden. Ich nehme an, dass er sich auf die mögliche Race Condition bezieht, bei der zwei Abonnenten (in der Nähe von) gleichzeitig versuchen, sich zu abonnieren, und sie beide am Ende abonnieren können. Eine Sperre könnte verwendet werden, um dies zu verbessern. Wenn Sie beabsichtigen, die Art und Weise zu ändern, wie das Ereignis-Abonnement funktioniert, rate ich Ihnen, dass Sie read about how to make the subscription add/remove properties thread safe.

+0

Ich denke, ich werde mit der Boolean Member Variable Ansatz weitermachen. Allerdings bin ich ein wenig überrascht, dass es keine andere Möglichkeit gibt zu überprüfen, ob ein Client bereits abonniert ist. Ich hätte gedacht, dass es für einen bestimmten Kunden eher üblich ist, nur einmal zu abonnieren? –

+1

Abhängig von Ihrer Konfiguration möchten Sie möglicherweise eine Ausnahme auslösen, wenn das Ereignis bereits einen Abonnenten hat. Wenn Sie Abonnenten zur Laufzeit hinzufügen, werden sie über den Fehler informiert, anstatt nichts zu tun. Das Ändern des Standardverhaltens ohne Benachrichtigung des Benutzers ist nicht die beste Vorgehensweise. –

+1

Dies ist nicht sicher für Multithreading. –

2

würden Sie müssen entweder ein separates Flag speichern angibt, ob oder nicht hatte Sie abonniert oder, wenn Sie die Kontrolle über MemberClass haben, bieten Implementierungen der Hinzufügen und Entfernen von Methoden für die Veranstaltung:

class MemberClass 
{ 
     private EventHandler _event; 

     public event EventHandler Event 
     { 
      add 
      { 
       if(/* handler not already added */) 
       { 
        _event+= value; 
       } 
      } 
      remove 
      { 
       _event-= value; 
      } 
     } 
} 

zu Entscheiden Sie, ob der Handler hinzugefügt wurde oder nicht, Sie müssen die von GetInvocationList() zurückgegebenen Delegates sowohl für _event als auch für value vergleichen.

7

Wie andere gezeigt haben, können Sie die Eigenschaften zum Hinzufügen/Entfernen des Ereignisses überschreiben. Alternativ können Sie das Ereignis abbrechen und die Klasse einfach dazu veranlassen, einen Delegaten als Argument in seinem Konstruktor (oder einer anderen Methode) zu verwenden, und anstatt das Ereignis auszulösen, rufen Sie den angegebenen Delegaten auf.

Ereignisse implizieren, dass jeder sie abonnieren kann, während ein Delegat eine Methode ist, die Sie an die Klasse übergeben können. Wahrscheinlich wird der Benutzer Ihrer Bibliothek dann weniger überrascht sein, wenn Sie Ereignisse nur dann verwenden, wenn Sie tatsächlich die Eins-zu-Viele-Semantik verwenden, die sie normalerweise anbietet.

47

Ich füge das in alle doppelten Fragen, nur für die Aufzeichnung. Dieses Muster für mich gearbeitet:

myClass.MyEvent -= MyHandler; 
myClass.MyEvent += MyHandler; 

Beachten Sie, dass jedes Mal, dies zu tun Sie Ihre Handler registrieren wird sichergestellt, dass der Handler nur einmal registriert ist.

+4

Funktioniert auch für mich, und das scheint mir die beste Lösung für Fälle, in denen Sie keinen Zugriff auf die Klasse haben (in meinem Fall Form.KeyDown) –

+4

gibt es einen Vorbehalt zu dieser Lösung. Wenn Sie zu einem Zeitpunkt, zu dem ein Event eintrifft, abgemeldet sind, verpassen Sie das Event. Stellen Sie daher sicher, dass zwischen dem Abbestellen und dem Abonnement keine 100% Ereignisse liegen. – Denis

+0

In meinem Fall, wo ich versuchte, WPF-Control-Event-Duplikation zu vermeiden, war diese Lösung der einfachste und sauberste Weg. –

4

U kann Postsharper verwenden, um ein Attribut nur einmal zu schreiben und es bei normalen Ereignissen zu verwenden. Den Code wiederverwenden. Codebeispiel ist unten angegeben.

[Serializable] 
public class PreventEventHookedTwiceAttribute: EventInterceptionAspect 
{ 
    private readonly object _lockObject = new object(); 
    readonly List<Delegate> _delegates = new List<Delegate>(); 

    public override void OnAddHandler(EventInterceptionArgs args) 
    { 
     lock(_lockObject) 
     { 
      if(!_delegates.Contains(args.Handler)) 
      { 
       _delegates.Add(args.Handler); 
       args.ProceedAddHandler(); 
      } 
     } 
    } 

    public override void OnRemoveHandler(EventInterceptionArgs args) 
    { 
     lock(_lockObject) 
     { 
      if(_delegates.Contains(args.Handler)) 
      { 
       _delegates.Remove(args.Handler); 
       args.ProceedRemoveHandler(); 
      } 
     } 
    } 
} 

Verwenden Sie es einfach so.

[PreventEventHookedTwice] 
public static event Action<string> GoodEvent; 

Details sehen Implement Postsharp EventInterceptionAspect to prevent an event Handler hooked twice

0

Es ist wie eine einfache Möglichkeit, mir scheint, dies zu tun ist Ihr Handler abmelden (die unbemerkt fehlschlagen, wenn sie nicht abonniert) und dann abonnieren.

member.Event -= eventHandler; 
member.Event += eventHandler; 

Es tut Ort die Belastung für den Entwickler, die den falschen Weg ist, es zu tun, aber für die schnelle und schmutzig ist dies schnell und schmutzig.