Un gestionnaire d'événements a-t-il déjà été ajouté?

183

Existe-t-il un moyen de savoir si un gestionnaire d'événements a été ajouté à un objet? Je sérialise une liste d'objets dans / hors état de session afin que nous puissions utiliser l'état de session basé sur SQL ... Lorsqu'un objet de la liste a une propriété modifiée, il doit être marqué, ce que le gestionnaire d'événements a pris en charge correctement avant . Cependant, maintenant, lorsque les objets sont désérialisés, il n'obtient pas le gestionnaire d'événements.

Dans un accès de gêne légère, je viens d'ajouter le gestionnaire d'événements à la propriété Get qui accède à l'objet. Il est appelé maintenant, ce qui est génial, sauf qu'il est appelé 5 fois, donc je pense que le gestionnaire continue d'être ajouté à chaque fois que l'objet est accédé.

C'est vraiment assez sûr pour être ignoré, mais je préfère le rendre beaucoup plus propre en vérifiant si le gestionnaire a déjà été ajouté, donc je ne le fais qu'une seule fois.

Est-ce possible?

EDIT: Je n'ai pas nécessairement un contrôle total sur les gestionnaires d'événements ajoutés, donc la simple vérification de null n'est pas suffisante.

CodeRedick
la source
voir aussi stackoverflow.com/questions/367523/…
Ian Ringrose

Réponses:

123

De l'extérieur de la classe de définition, comme @Telos le mentionne, vous ne pouvez utiliser EventHandler que sur le côté gauche de a +=ou a -=. Donc, si vous avez la possibilité de modifier la classe de définition, vous pouvez fournir une méthode pour effectuer la vérification en vérifiant si le gestionnaire d'événements est null- si tel est le cas, aucun gestionnaire d'événements n'a été ajouté. Sinon, alors peut-être et vous pouvez parcourir les valeurs dans Delegate.GetInvocationList . Si l'un est égal au délégué que vous souhaitez ajouter en tant que gestionnaire d'événements, vous savez qu'il est là.

public bool IsEventHandlerRegistered(Delegate prospectiveHandler)
{   
    if ( this.EventHandler != null )
    {
        foreach ( Delegate existingHandler in this.EventHandler.GetInvocationList() )
        {
            if ( existingHandler == prospectiveHandler )
            {
                return true;
            }
        }
    }
    return false;
}

Et cela pourrait facilement être modifié pour devenir "ajouter le gestionnaire s'il n'y est pas". Si vous n'avez pas accès aux entrailles de la classe qui expose l'événement, vous devrez peut-être explorer -=et +=, comme suggéré par @Lou Franco.

Cependant, vous feriez peut-être mieux de réexaminer la manière dont vous mettez en service et désactivez ces objets, pour voir si vous ne pouvez pas trouver un moyen de suivre ces informations vous-même.

Blair Conrad
la source
7
Cela ne compile pas, EventHandler ne peut être que sur le côté gauche de + = ou - =.
CodeRedick le
2
Suppression du vote après explication supplémentaire. L'état SQL détruit à peu près toute l'idée ici ... :(
CodeRedick
1
Merci Blair et SO recherchez, exactement ce que je cherchais (ennuyeux, vous ne pouvez pas le faire en dehors de la classe)
George Mauer
3
Le problème survient la plupart du temps lors de la comparaison des délégués pour l'égalité. Utilisez donc Delegate.Equals(objA, objB)si vous voulez vérifier exactement la même existence de délégué. Sinon, comparez les propriétés individuellement comme if(objA.Method.Name == objB.Method.Name && objA.Target.GetType().FullName == objB.Target.GetType().FullName).
Sanjay
2
Ce code ne fonctionne pas dans un WinForm. Est-ce strictement pour ASP.NET?
jp2code
213

Je suis récemment arrivé à une situation similaire où je devais enregistrer un gestionnaire pour un événement une seule fois. J'ai trouvé que vous pouvez d'abord vous désinscrire en toute sécurité, puis vous réinscrire, même si le gestionnaire n'est pas du tout enregistré:

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

Notez que faire cela à chaque fois que vous enregistrez votre gestionnaire garantira que votre gestionnaire n'est enregistré qu'une seule fois. Cela me semble être une très bonne pratique :)

alf
la source
9
Semble risqué; si un événement est déclenché après avoir supprimé le gestionnaire et avant de le rajouter, il sera manqué.
Jimmy
27
Sûr. Vous voulez dire que ce n'est pas thread-safe. Mais cela ne peut être un problème que lors de l'exécution de plusieurs threads ou similaires, ce qui n'est pas habituel. Dans la plupart des cas, cela devrait être suffisant pour des raisons de simplicité.
alf
7
Cela me dérange. Ce n'est pas parce que vous ne créez pas actuellement explicitement de threads dans votre code qu'il n'y a pas plusieurs threads ou qu'ils ne seront pas ajoutés plus tard. Dès que vous (ou quelqu'un d'autre dans l'équipe, peut-être des mois plus tard) ajoutez un thread de travail ou répondez à la fois à l'interface utilisateur et à la connexion réseau, cela ouvre la porte à des événements abandonnés très intermittents.
Technophile
1
Je pense que la fenêtre de temps entre la suppression et l'ajout de nouveau est si petite qu'il est très peu probable qu'il existe des événements manqués, mais oui, c'est toujours possible.
Alisson
2
Si vous utilisez ceci pour quelque chose comme la mise à jour d'une interface utilisateur, etc., alors c'est une tâche si triviale que le risque est correct. Si c'était pour la gestion des paquets réseau, etc., je ne l'utiliserais pas.
lance le
18

S'il s'agit du seul gestionnaire, vous pouvez vérifier si l'événement est nul, sinon, le gestionnaire a été ajouté.

Je pense que vous pouvez appeler en toute sécurité - = sur l'événement avec votre gestionnaire même s'il n'est pas ajouté (sinon, vous pouvez l'attraper) - pour vous assurer qu'il n'est pas là avant de l'ajouter.

Lou Franco
la source
3
Cette logique s'interrompt dès que l'événement est également géré ailleurs.
buggé87
6

Cet exemple montre comment utiliser la méthode GetInvocationList () pour récupérer des délégués à tous les gestionnaires qui ont été ajoutés. Si vous cherchez à voir si un gestionnaire (fonction) spécifique a été ajouté, vous pouvez utiliser array.

public class MyClass
{
  event Action MyEvent;
}

...

MyClass myClass = new MyClass();
myClass.MyEvent += SomeFunction;

...

Action[] handlers = myClass.MyEvent.GetInvocationList(); //this will be an array of 1 in this example

Console.WriteLine(handlers[0].Method.Name);//prints the name of the method

Vous pouvez examiner diverses propriétés de la propriété Method du délégué pour voir si une fonction spécifique a été ajoutée.

Si vous cherchez à voir s'il n'y en a qu'un seul attaché, vous pouvez simplement tester null.

Jason Jackson
la source
GetInvocationList () n'est pas membre de ma classe. En fait, je n'arrive pas à trouver cette méthode sur aucun objet ou gestionnaire
auquel
J'avais essayé ça aussi, apparemment on ne peut accéder à l'événement comme ça qu'à l'intérieur de la classe. Je fais cela de manière générique, et comme d'autres l'ont mentionné, les gestionnaires d'événements se perdent probablement de toute façon. Merci pour la clarification!
CodeRedick le
4

Si je comprends bien votre problème, vous pouvez avoir des problèmes plus importants. Vous avez dit que d'autres objets peuvent souscrire à ces événements. Lorsque l'objet est sérialisé et désérialisé, les autres objets (ceux dont vous n'avez pas le contrôle) perdront leurs gestionnaires d'événements.

Si cela ne vous inquiète pas, garder une référence à votre gestionnaire d'événements devrait suffire. Si vous êtes préoccupé par les effets secondaires d'autres objets perdant leurs gestionnaires d'événements, vous voudrez peut-être repenser votre stratégie de mise en cache.

CodeChef
la source
1
Oh! Je n'avais même pas pensé à cela ... même si cela aurait dû être évident étant donné que mon problème initial était que mon propre gestionnaire se perdait.
CodeRedick le
2

Je suis d'accord avec la réponse d'alf, mais il y a peu de modifications à y apporter, à utiliser,

           try
            {
                control_name.Click -= event_Click;
                main_browser.Document.Click += Document_Click;
            }
            catch(Exception exce)
            {
                main_browser.Document.Click += Document_Click;
            }
Développeur de logiciels
la source
2

La seule façon qui a fonctionné pour moi est de créer une variable booléenne que j'ai définie sur true lorsque j'ajoute l'événement. Alors je demande: si la variable est fausse, j'ajoute l'événement.

bool alreadyAdded = false;

Cette variable peut être globale.

if(!alreadyAdded)
{
    myClass.MyEvent += MyHandler;
    alreadyAdded = true;
}
Xtian11
la source
0
EventHandler.GetInvocationList().Length > 0
benPearce
la source
2
cela ne lance-t-il pas lorsque la liste == null?
Boris Callens
2
en dehors de la classe possédant le gestionnaire d'événements, vous ne pouvez utiliser que - = et + =. vous ne pouvez pas accéder à l'événement.
tbergelt