C # pattern pour empêcher un handler d'événement accroché deux fois [dupliquer]

cette question a déjà une réponse ici:

duplicata de: comment s'assurer qu'un événement n'est souscrit qu'une seule fois et est-ce qu'un gestionnaire d'événements a déjà été ajouté?

j'ai un singleton qui fournit quelques services et mes cours de crochet dans certains événements sur, parfois, une classe est d'accrocher deux fois à l'événement, puis est appelée deux fois. Je cherche un moyen classique d'empêcher ça. je dois vérifier si je suis déjà accro à cet événement...

78
demandé sur Community 2009-06-02 02:46:49

9 réponses

implémente explicitement l'événement et vérifie la liste des invocations. Vous aurez également besoin de vérifier Pour null:

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

en utilisant le code ci-dessus, si un appelant s'abonne à l'événement plusieurs fois, il sera tout simplement ignoré.

128
répondu Judah Himango 2011-11-11 21:31:23

Que Diriez-vous de simplement supprimer l'événement d'abord avec -= , s'il n'est pas trouvé une exception n'est pas jeté

/// -= 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);
153
répondu PrimeTSS 2011-02-21 22:18:49

j'ai testé chaque solution et la meilleure (compte tenu de la performance) est:

private EventHandler _foo;
public event EventHandler Foo {

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

Pas de Linq l'aide nécessaire. Nul besoin de vérifier avant d'annuler un Abonnement (voir MS EventHandler pour plus de détails). Pas besoin de se rappeler de faire le désabonnement partout.

24
répondu LoxLox 2013-06-20 20:15:55

vous devriez vraiment gérer cela au niveau de l'évier et non au niveau de la source. C'est - à-dire, ne prescrivez pas la logique du gestionnaire d'événements à la source de l'événement-laissez cela aux gestionnaires (les éviers) eux-mêmes.

en tant que développeur d'un service, qui êtes-vous pour dire que les éviers ne peuvent s'enregistrer qu'une seule fois? Que faire si ils veulent s'inscrire deux fois pour une raison quelconque? Et si vous essayez de corriger les bugs dans les éviers en modifiant la source, c'est encore une bonne raison pour corriger ces les questions à l'évier.

je suis sûr que vous avez vos raisons; une source d'événement pour laquelle les éviers dupliqués sont illégaux n'est pas insondable. Mais peut-être devriez-vous envisager une architecture alternative qui laisse intacte la sémantique d'un événement.

18
répondu JP Alioto 2015-08-11 20:31:56

vous devez implémenter l'add et supprimer les accesseurs sur l'événement, puis vérifier la liste des cibles du délégué, ou stocker les cibles dans une liste.

dans la méthode add, vous pouvez utiliser le délégué.GetInvocationList méthode pour obtenir une liste des objectifs déjà ajoutés au délégué.

puisque les délégués sont définis pour comparer equal s'ils sont liés à la même méthode sur le même objet cible, vous pourriez probablement exécutez dans cette liste et comparez, et si vous ne trouvez aucun qui compare égal, vous ajoutez le nouveau.

voici un exemple de code, compiler comme application de la console:

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

sortie:

tc_Test called

(ie. une seule fois)

12
répondu Lasse Vågsæther Karlsen 2009-06-01 23:00:44

Microsoft Reactive Extensions (Rx) framework peut également être utilisé pour faire"subscribe only once".

donne un événement de souris foo.Cliqué, voici comment s'abonner et ne recevoir qu'une seule invocation:

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

en plus de fournir la fonctionnalité" s'abonner une fois", L'approche RX offre la possibilité de composer des événements ensemble ou filtrer des événements. C'est assez chouette.

6
répondu Judah Himango 2010-08-13 14:28:01

créer une Action au lieu d'un événement. Votre classe peut ressembler à:

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

}
1
répondu Tono Nam 2013-06-19 14:35:47

faites vérifier par votre objet singleton la liste des personnes qu'il avertit et n'appelez qu'une fois s'il est dupliqué. Alternativement si possible rejeter la demande de pièce jointe d'événement.

0
répondu Toby Allen 2009-06-01 22:56:48

à silverlight vous devez dire E. Handled = true; in the event code.

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

veuillez me cocher si cela peut aider.

0
répondu Omzig 2012-08-23 18:24:10