Question

J'utilise C #, .NET 3.5. Je comprends comment utiliser les événements, comment les déclarer dans ma classe, comment les accrocher ailleurs, etc. Un exemple artificiel:

public class MyList
{
    private List<string> m_Strings = new List<string>();
    public EventHandler<EventArgs> ElementAddedEvent;

    public void Add(string value)
    {
        m_Strings.Add(value);
        if (ElementAddedEvent != null)
            ElementAddedEvent(value, EventArgs.Empty);
    }
}

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        MyList tmp = new MyList();
        tmp.ElementAddedEvent += new EventHandler<EventArgs>(Fired);
        tmp.Add("test");
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

Cependant, ce que je ne comprends pas , c’est quand on déclare un gestionnaire d’événements

public EventHandler<EventArgs> ElementAddedEvent;

Il n’a jamais été initialisé. En quoi consiste exactement ElementAddedEvent? Qu'est-ce que cela pointe? Les éléments suivants ne fonctionnent pas, car le gestionnaire d'événements n'est jamais initialisé:

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        EventHandler<EventArgs> somethingHappend;
        somethingHappend += new EventHandler<EventArgs>(Fired);
        somethingHappend(this, EventArgs.Empty);
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

Je remarque qu’il existe un EventHandler.CreateDelegate (...), mais toutes les signatures de méthode suggèrent que cela n’est utilisé que pour attacher des délégués à un EventHandler existant via le typique ElementAddedEvent + = new EventHandler (MyMethod).

Je ne sais pas si ce que j'essaie de faire va aider ... mais au final, j'aimerais proposer un parent abstrait DataContext dans LINQ dont les enfants peuvent enregistrer quelle table ils veulent " observés " je peux donc avoir des événements tels que BeforeUpdate et AfterUpdate, mais spécifiques aux types. Quelque chose comme ça:

public class BaseDataContext : DataContext
{
    private static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> m_ObservedTypes = new Dictionary<Type, Dictionary<ChangeAction, EventHandler>>();

    public static void Observe(Type type)
    {
        if (m_ObservedTypes.ContainsKey(type) == false)
        {
            m_ObservedTypes.Add(type, new Dictionary<ChangeAction, EventHandler>());

            EventHandler eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Insert, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Update, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Delete, eventHandler);
        }
    }

    public static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> Events
    {
        get { return m_ObservedTypes; }
    }
}


public class MyClass
{
    public MyClass()
    {
        BaseDataContext.Events[typeof(User)][ChangeAction.Update] += new EventHandler(OnUserUpdate);
    }

    public void OnUserUpdated(object sender, EventArgs args)
    {
        // do something
    }
}

En réfléchissant à cela, je me suis rendu compte que je ne comprenais pas vraiment ce qui se passait sous le feu des événements - et j'aimerais comprendre:)

Était-ce utile?

La solution

Je l'ai écrit de manière assez détaillée dans un article . , mais voici le résumé, en supposant que vous êtes raisonnablement satisfait des délégués eux-mêmes. :

  • Un événement est simplement un " add " méthode et un "supprimer" méthode, de la même manière qu’une propriété n’est en réalité qu’un "get" méthode et un " set " méthode. (En fait, la CLI autorise également une méthode "raise / fire", mais C # ne la génère jamais.) Les métadonnées décrivent l'événement en faisant référence aux méthodes.
  • Lorsque vous déclarez un événement de type champ (comme votre ElementAddedEvent), le compilateur génère les méthodes et un champ privé (du même type que le délégué). Dans la classe, lorsque vous faites référence à ElementAddedEvent, vous faites référence au champ. En dehors de la classe, vous faites référence au champ.
  • Lorsque quelqu'un s'abonne à un événement (avec l'opérateur + =) qui appelle la méthode add. Quand ils se désabonnent (avec l'opérateur - =), ils appellent le supprimer.
  • Pour les événements de type champ, il existe une synchronisation, mais sinon, il suffit d'appeler / de supprimer l'appel de délégué. Combiner / Supprimer pour modifier la valeur du champ généré automatiquement. Ces deux opérations affectent le champ de sauvegarde - rappelez-vous que les délégués sont immuables. En d'autres termes, le code généré automatiquement ressemble beaucoup à ceci:

    // Backing field
    // The underscores just make it simpler to see what's going on here.
    // In the rest of your source code for this class, if you refer to
    // ElementAddedEvent, you're really referring to this field.
    private EventHandler<EventArgs> __ElementAddedEvent;
    
    // Actual event
    public EventHandler<EventArgs> ElementAddedEvent
    {
        add
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent += value;
                __ElementAddedEvent = Delegate.Combine(__ElementAddedEvent, value);
            }
        }
        remove
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent -= value;
                __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value);
            }
        }
    }
    
  • La valeur initiale du champ généré dans votre cas est null - et redeviendra toujours null si tous les abonnés sont supprimés, car c'est le comportement de Delegate.Remove.

  • Si vous voulez un " no-op " gestionnaire pour vous abonner à votre événement, afin d'éviter le contrôle de nullité, vous pouvez faire:

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

    Le delegate {} est simplement une méthode anonyme qui ne se soucie pas de ses paramètres et ne fait rien.

Si quelque chose n'est pas encore clair, demandez-le-moi et j'essaierai de vous aider!

Autres conseils

Sous le capot, les événements ne sont que des délégués dotés de conventions spéciales. (Par exemple, il n'est pas nécessaire de vérifier la nullité avant de déclencher un événement.)

Dans le pseudocode, Event.Invoke () se décompose comme suit:

Si l'événement a des auditeurs   Appelez chaque auditeur de manière synchrone sur ce fil dans un ordre arbitraire.

Étant donné que les événements sont en multidiffusion, ils n'auront aucun écouteur ou plus dans une collection. Le CLR les parcourera en boucle, les appelant dans un ordre arbitraire.

Il convient de garder à l’esprit que les gestionnaires d’événements s’exécutent dans le même fil de discussion que celui-ci. C’est une erreur mentale courante de penser qu’ils créent un nouveau fil de discussion. Ils ne le font pas.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top