2008-12-15 10 views
35

Je voudrais m'assurer que je ne m'abonne qu'une fois dans une classe particulière pour un événement sur une instance.Comment m'assurer qu'un événement n'est souscrit qu'une seule fois

Par exemple, je voudrais être en mesure de faire ce qui suit:

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

Comment pourrais-je aller sur la mise en œuvre d'une telle garde?

Répondre

31

Si vous parlez d'un événement sur une classe pour laquelle vous avez accès à la source, vous pouvez placer le garde dans la définition de l'événement.

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

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

Cela garantirait qu'un seul abonné peut s'abonner à l'événement sur cette instance de la classe qui fournit l'événement.

EDIT S'il vous plaît voir les commentaires sur pourquoi le code ci-dessus est une mauvaise idée et non thread thread

Si votre problème est qu'une seule instance du client s'abonne plusieurs fois (et que vous avez besoin de plusieurs abonnés), alors le code client devra gérer cela. Donc, remplacer

pas déjà inscrit

avec un membre bool de la classe client qui obtient l'ensemble lorsque vous vous abonnez pour l'événement la première fois.

Modifier (après acceptée): D'après le commentaire de @Glen T (l'émetteur de la question) le code de la solution retenue, il est allé avec est dans la classe client:

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

Où alreadySubscribedFlag est une variable membre de la classe client qui suit le premier abonnement à l'événement spécifique. Les gens qui regardent le premier extrait de code ici, s'il vous plaît prendre note du commentaire @ Rune - ce n'est pas une bonne idée de changer le comportement de l'abonnement à un événement d'une manière non évidente.

EDIT 31/7/2009: Veuillez vous référer aux commentaires de @Sam Saffron. Comme je l'ai déjà dit et Sam admet que la première méthode présentée ici n'est pas un moyen sensé de modifier le comportement de l'abonnement à l'événement. Les consommateurs de la classe doivent connaître sa mise en œuvre interne pour comprendre son comportement. Pas très gentil.
@Sam Saffron commente également la sécurité des threads. Je suppose qu'il fait référence à la condition de concurrence possible où deux abonnés (proches de) tentent simultanément de s'abonner et ils peuvent tous les deux finir par s'abonner. Un verrou pourrait être utilisé pour améliorer cela. Si vous envisagez de changer le mode de fonctionnement de l'abonnement à l'événement, je vous conseille de read about how to make the subscription add/remove properties thread safe.

+0

Je pense que je vais aller de l'avant avec l'approche variable booléenne. Cependant, je suis un peu surpris qu'il n'y ait pas un autre moyen de vérifier si un client est déjà inscrit. J'aurais pensé qu'il était plus commun pour un client donné de vouloir seulement s'abonner une fois? –

+1

Selon votre configuration, vous pouvez lancer une exception si l'événement a déjà un abonné. Si vous ajoutez des abonnés à l'exécution, cela les avertira de l'erreur au lieu de ne rien faire. Changer le comportement par défaut sans avertir l'utilisateur n'est pas la meilleure pratique. –

+1

Ceci n'est pas sûr pour le multithreading. –

2

Vous soit besoin de stocker un drapeau distinct indiquant si vous aviez ou non souscrit ou, si vous avez le contrôle sur MemberClass, fournit des implémentations des ajouter et supprimer des méthodes pour l'événement:

class MemberClass 
{ 
     private EventHandler _event; 

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

Pour décidez si le gestionnaire a été ajouté ou non, vous devrez comparer les délégués renvoyés par GetInvocationList() à la fois sur _event et value.

7

Comme d'autres l'ont montré, vous pouvez remplacer les propriétés d'ajout/de suppression de l'événement. Vous pouvez également abandonner l'événement et demander à la classe de prendre un délégué en tant qu'argument dans son constructeur (ou une autre méthode), et au lieu de déclencher l'événement, appelez le délégué fourni.

Les événements impliquent que n'importe qui peut s'y abonner, alors qu'un délégué est une méthode que vous pouvez transmettre à la classe. Sera probablement moins surprenant pour l'utilisateur de votre bibliothèque, si vous n'utilisez des événements que lorsque vous utilisez réellement la sémantique un-à-plusieurs qu'il offre habituellement.

47

J'ajoute ceci dans toutes les questions en double, juste pour l'enregistrement. Ce modèle a fonctionné pour moi:

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

Notez que faire cela chaque fois que vous enregistrez votre gestionnaire veillera à ce que votre gestionnaire est enregistré qu'une seule fois.

+4

Fonctionne pour moi aussi, et cela me semble être la meilleure solution pour les cas où vous n'avez pas accès à la classe (dans mon cas Form.KeyDown) –

+4

il ya une mise en garde à cette solution. Si vous êtes désabonné à un moment où un événement arrive, vous ratez l'événement alors assurez-vous qu'aucun événement ne se produit entre la désinscription et l'abonnement. – Denis

+0

Dans mon cas, si j'essayais d'éviter la duplication d'événements de contrôle WPF, cette solution était la plus simple et la plus propre. –

4

U peut utiliser Postsharper pour écrire un attribut une seule fois et l'utiliser sur des événements normaux. Réutilisez le code. L'échantillon de code est donné ci-dessous.

[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(); 
      } 
     } 
    } 
} 

Utilisez-le comme ceci.

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

Pour plus de détails regarder Implement Postsharp EventInterceptionAspect to prevent an event Handler hooked twice

0

Il me semble comme un moyen facile de le faire est de vous désabonner de votre gestionnaire (qui échouera en silence sinon souscrit) et puis abonnez-vous.

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

Il -t porter le poids sur le développeur, ce qui est la mauvaise façon de le faire, mais pour ce rapide et sale est rapide et sale.