web-dev-qa-db-fra.com

Modèle C # pour empêcher un gestionnaire d'événement accroché deux fois

Duplicate of: Comment s'assurer qu'un événement n'est souscrit qu'une seule fois et n gestionnaire d'événements a-t-il déjà été ajouté?

J'ai un singleton qui fournit certains services et mes classes sont associées à certains événements. Parfois, une classe se connecte deux fois à l'événement puis est appelée deux fois. Je cherche un moyen classique d'éviter cela. en quelque sorte, je dois vérifier si je suis déjà accro à cet événement ...

90
Ali Shafai

Implémentez explicitement l'événement et vérifiez la liste d'appels. Vous devrez également vérifier la valeur 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 simplement ignoré.

143

Pourquoi ne pas simplement supprimer l'événement en premier avec -=, s'il n'est pas trouvé, une exception n'est pas levée

/// -= 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

J'ai testé chaque solution et la meilleure (en termes de performances) est:

private EventHandler _foo;
public event EventHandler Foo {

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

Aucune utilisation de Linq requise. Pas besoin de vérifier la valeur null avant d'annuler un abonnement (voir MS EventHandler pour plus de détails). Pas besoin de penser à faire la désinscription partout.

33
LoxLox

Vous devriez vraiment gérer cela au niveau de l'évier et non au niveau de la source. En d’autres termes, ne spécifiez pas la logique du gestionnaire d’événements à la source de l’événement, laissez cette tâche aux gestionnaires (les récepteurs) eux-mêmes.

En tant que développeur d’un service, qui êtes-vous pour dire que les puits ne peuvent s’inscrire qu’une seule fois? Et s'ils veulent s'inscrire deux fois pour une raison quelconque? Et si vous essayez de corriger des bugs dans les puits en modifiant la source, c'est à nouveau une bonne raison de corriger ces problèmes au niveau du puits.

Je suis sûr que vous avez vos raisons une source d'événement pour laquelle les puits de doublons 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.

19
JP Alioto

Vous devez implémenter les accesseurs ajouter et supprimer 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 la méthode Delegate.GetInvocationList pour obtenir la liste des cibles déjà ajoutées au délégué.

Étant donné que les délégués sont définis pour être comparés égaux s'ils sont liés à la même méthode sur le même objet cible, vous pouvez probablement parcourir cette liste et comparer, et si vous n'en trouvez aucun qui se compare, vous ajoutez le nouveau.

Voici un exemple de code à compiler en application 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

(c'est-à-dire une seule fois)

La version Framework de Reactive Extensions (Rx) de Microsoft peut également être utilisée pour "s'abonner une seule fois".

Étant donné un événement de souris foo.Clicked, voici comment vous abonner et recevoir un seul appel:

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 de les filtrer. C'est assez chouette.

6

Créez 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);
     }

}
2
Tono Nam

En argent, vous devez dire e.Handled = true; dans le code de l'événement.

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

Cochez-moi s'il vous plaît si cela aide.

0
Omzig

demandez à votre objet singleton de vérifier la liste des personnes qu’il avise et d’appeler une seule fois s’il est dupliqué. Sinon, si possible, rejeter la demande de pièce jointe à un événement.

0
Toby Allen