web-dev-qa-db-fra.com

C # Comment savoir si un événement est connecté

Je veux pouvoir savoir si un événement est lié ou non. J'ai regardé autour de moi, mais je n'ai trouvé que des solutions qui impliquaient de modifier les internes de l'objet qui contient l'événement. Je ne veux pas faire ça.

Voici un code de test qui, selon moi, fonctionnerait:

// Create a new event handler that takes in the function I want to execute when the event fires
EventHandler myEventHandler = new EventHandler(myObject_SomeEvent);
// Get "p1" number events that got hooked up to myEventHandler
int p1 = myEventHandler.GetInvocationList().Length;
// Now actually hook an event up
myObject.SomeEvent += m_myEventHandler;
// Re check "p2" number of events hooked up to myEventHandler
int p2 = myEventHandler.GetInvocationList().Length;

Malheureusement, ce qui précède est complètement faux. Je pensais que "invocationList" dans myEventHandler serait automatiquement mis à jour lorsque j'y accrocherais un événement. Mais non, ce n'est pas le cas. La longueur de cela revient toujours comme un.

Existe-t-il de toute façon de le déterminer de l'extérieur de l'objet qui contient l'événement?

37
Nick

Il y a une illusion subtile présentée par le mot clé C # event et c'est qu'un événement a une liste d'invocation.

Si vous déclarez l'événement à l'aide du mot clé C # event, le compilateur générera un délégué privé dans votre classe et le gérera pour vous. Chaque fois que vous vous abonnez à l'événement, la méthode add générée par le compilateur est invoquée, ce qui ajoute le gestionnaire d'événements à la liste d'invocation du délégué. Il n'y a pas de liste d'invocation explicite pour l'événement.

Ainsi, la seule façon d'accéder à la liste d'invocation du délégué est de préférence:

  • Utilisez la réflexion pour accéder au délégué généré par le compilateur OU
  • Créez un délégué non privé (peut-être interne) et implémentez manuellement les méthodes d'ajout/suppression de l'événement (cela empêche le compilateur de générer l'implémentation par défaut de l'événement)

Voici un exemple illustrant cette dernière technique.

class MyType
{
    internal EventHandler<int> _delegate;
    public event EventHandler<int> MyEvent;
    {
        add { _delegate += value; }
        remove { _delegate -= value; }
    }
}
50
Steve Guidi

Si l'objet concerné a spécifié le mot clé event, alors les seules choses que vous pouvez faire sont ajouter (+=) et supprimer (-=) gestionnaires, rien de plus.

Je crois que la comparaison de la longueur de la liste d'invocation fonctionnerait, mais vous devez utiliser à l'intérieur l'objet pour y accéder.

Gardez également à l'esprit que le += et -= les opérateurs renvoient un nouvel objet événement; ils n'en modifient pas un existant.

Pourquoi voulez-vous savoir si un événement particulier est lié? Est-ce pour éviter de s'inscrire plusieurs fois?

Si c'est le cas, l'astuce consiste à supprimer d'abord le gestionnaire (-=) comme supprimer un gestionnaire qui n'est pas là est légal et ne fait rien. Par exemple:

// Ensure we don't end up being triggered multiple times by the event
myObject.KeyEvent -= KeyEventHandler;
myObject.KeyEvent += KeyEventHandler;
64
Bevan

Cela peut être fait, mais il faut du piratage ... comme mentionné ci-dessus, le compilateur génère l'implémentation de l'événement, y compris son champ de support. La réflexion vous permet de récupérer le champ de sauvegarde par nom, et une fois que vous y avez accès, vous pouvez appeler GetInvocationList() même si vous êtes en dehors de la classe elle-même.

Puisque vous demandez à utiliser la réflexion pour obtenir l'événement par nom, je suppose que vous utilisez également la réflexion pour obtenir le type par nom - je prépare un exemple qui montrera comment le faire.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string typeName = "ConsoleApplication1.SomeClass, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
            string eventName = "SomeEvent";

            Type declaringType = Type.GetType(typeName);
            object target = Activator.CreateInstance(declaringType);

            EventHandler eventDelegate;
            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null) { Console.WriteLine("No listeners"); }

            // attach a listener
            SomeClass bleh = (SomeClass)target;
            bleh.SomeEvent += delegate { };
            //

            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null)
            { 
                Console.WriteLine("No listeners"); 
            }
            else
            { 
                Console.WriteLine("Listeners: " + eventDelegate.GetInvocationList().Length); 
            }

            Console.ReadKey();

        }

        static EventHandler GetEventHandler(object classInstance, string eventName)
        {
            Type classType = classInstance.GetType();
            FieldInfo eventField = classType.GetField(eventName, BindingFlags.GetField
                                                               | BindingFlags.NonPublic
                                                               | BindingFlags.Instance);

            EventHandler eventDelegate = (EventHandler)eventField.GetValue(classInstance);

            // eventDelegate will be null if no listeners are attached to the event
            if (eventDelegate == null)
            {
                return null;
            }

            return eventDelegate;
        }
    }

    class SomeClass
    {
        public event EventHandler SomeEvent;
    }
}
13
STW

Vous devriez pouvoir obtenir la liste d'invocation via "l'événement". En gros, ce sera quelque chose comme ..

public delegate void MyHandler;
public event MyHandler _MyEvent
public int GetInvocationListLength()
{
   var d = this._MyEvent.GetInvocationList(); //Delegate[]
   return d.Length;
}
5
user20155