40 votes

Comment les événements C # fonctionnent-ils en coulisse?

Je suis à l'aide de C#, .NET 3.5. Je comprends comment utiliser les événements, la façon de les déclarer dans ma classe, comment les connecter à partir de quelque part d'autre, 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 pas comprendre, c'est quand on déclare un gestionnaire d'événements

public EventHandler<EventArgs> ElementAddedEvent;

Il n'est jamais initialisée - alors quoi, exactement, est ElementAddedEvent? En quoi est-il point? Le suivant ne fonctionnera pas, parce que le Gestionnaire n'est jamais initialisée:

[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 y a un Gestionnaire d'événements.CreateDelegate(...), mais toutes les signatures de méthode suggère qu'il est seulement utilisé pour la fixation des Délégués à un Gestionnaire d'événements à travers le ElementAddedEvent += new EventHandler(MyMethod).

Je ne sais pas si ce que je suis en train de faire va les aider... mais finalement, je voudrais venir avec un résumé parent DataContext dans LINQ dont les enfants peuvent s'inscrire la table des Types qu'ils veulent ", a observé", donc je peux avoir des événements tels que BeforeUpdate et AfterUpdate, mais qui sont spécifiques à des types. Quelque chose comme ceci:

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

La réflexion sur ce qui m'a fait réaliser que je ne comprends pas vraiment ce qui se passe sous l'auge avec des événements - et j'aimerais comprendre :)

68voto

Jon Skeet Points 692016

J'ai écrit ce jusqu'à une bonne quantité de détails dans un article, mais voici le résumé, en supposant que vous êtes raisonnablement heureux avec les délégués eux-mêmes:

  • Un événement est juste une "ajouter" de la méthode et de "supprimer" la méthode, de la même manière qu'une propriété est vraiment juste une méthode "get" et "set" de la méthode. (En fait, la CLI permet de "lever/feu" méthode, mais C# génère jamais de ce sujet.) Les métadonnées décrit l'événement avec des références à des méthodes.
  • Lorsque vous déclarez un terrain comme événement (comme votre ElementAddedEvent) le compilateur génère les méthodes et un champ privé (du même type que le délégué). Au sein de la classe, lorsque vous vous référez à ElementAddedEvent vous faites référence au champ. En dehors de la classe, vous faites référence à ce domaine.
  • Lorsqu'une personne s'abonne à un événement (avec l'opérateur+=) qui appelle la méthode add. Quand ils se désabonner (avec l'opérateur=) qui appelle la supprimer.
  • Le terrain, comme les événements, il y a une synchronisation mais sinon, l'ajout/suppression de simplement appeler Délégué.Combiner/Supprimer pour modifier la valeur de l'auto-champ généré. Deux de ces opérations, de céder à la sauvegarde de champ - rappelez-vous que les délégués sont immuables. En d'autres termes, le code généré automatiquement est très bien comme cela:

    // 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 il y aura toujours des null nouveau si tous les abonnés sont supprimés, comme c'est le comportement de Délégué.Retirez.

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

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

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

Si il ya quelque chose qui est toujours pas clair, veuillez demander et je vais essayer de vous aider!

-3voto

Yes - that Jake. Points 9184

Sous le capot, les événements sont juste des délégués spéciaux conventions d'appel. (Par exemple, vous n'avez pas à vérifier la nullité avant de déclencher un événement.)

En pseudo-code, un Événement.Invoke() se décompose comme ceci:

Si L'Événement A Auditeurs Appel chaque auditeur de manière synchrone sur ce fil dans un ordre arbitraire.

Étant donné que les événements sont multicast, ils vont avoir zéro, un ou plusieurs auditeurs, tenue dans une collection. Le CLR boucle à travers eux, l'appel de chaque dans un ordre arbitraire.

Un gros bémol à retenir est que les gestionnaires d'événements sont exécutés dans le même thread que l'événement est déclenché dans. C'est une commune mentale erreur de penser que la reproduction d'un nouveau thread. Ils ne le font pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X