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

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 m_Ssortingngs = new List(); public EventHandler ElementAddedEvent; public void Add(ssortingng value) { m_Ssortingngs.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(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énement

 public EventHandler ElementAddedEvent; 

Ce n’est jamais initialisé – alors, qu’est-ce qu’ElementAddedEvent exactement? Qu’est-ce que cela signifie? Ce qui suit ne fonctionnera 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 somethingHappend; somethingHappend += new EventHandler(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 EventHandler.CreateDelegate (…), mais toutes les signatures de méthode suggèrent que cela est uniquement utilisé pour attacher des delegates à un gestionnaire d’événements existant via le gestionnaire d’élément ElementAddedEvent + = new (MyMethod).

Je ne suis pas sûr que ce que j’essaie de faire aidera … mais au final, j’aimerais proposer un parent abstrait DataContext dans LINQ dont les enfants peuvent enregistrer les types de table qu’ils veulent “observer” pour que je puisse 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> m_ObservedTypes = new Dictionary<Type, Dictionary>(); public static void Observe(Type type) { if (m_ObservedTypes.ContainsKey(type) == false) { m_ObservedTypes.Add(type, new Dictionary()); 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> 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 à cette question, je me suis rendu compte que je ne comprenais pas vraiment ce qui se passait sous le vent des événements – et j’aimerais bien comprendre 🙂

J’ai écrit ceci dans un article assez détaillé, mais voici le résumé, en supposant que vous êtes raisonnablement satisfait des delegates eux-mêmes:

  • Un événement est juste une méthode “add” et une méthode “remove”, de la même manière qu’une propriété est simplement une méthode “get” et une méthode “set”. (En fait, la CLI permet également une méthode “raise / fire”, mais C # ne la génère jamais.) Les métadonnées décrivent l’événement avec des références 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 terrain.
  • 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 – =) qui appelle le retrait.
  • Pour les événements de type champ, il y a une certaine synchronisation mais sinon, l’ajout / suppression appelle simplement Delegate. 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 delegates sont immuables. En d’autres termes, le code généré automatiquement est très similaire à 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 __ElementAddedEvent; // Actual event public EventHandler 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 redeviendra toujours null si tous les abonnés sont supprimés, comme cela est le comportement de Delegate.Remove.

  • Si vous souhaitez qu’un gestionnaire “no-op” s’abonne à votre événement, afin d’éviter le contrôle de la nullité, vous pouvez le faire:

     public EventHandler ElementAddedEvent = delegate {}; 

    Le delegate {} est juste une méthode anonyme qui ne se soucie pas de ses parameters et ne fait rien.

S’il y a quelque chose qui n’est pas encore clair, veuillez demander et je vais essayer de vous aider!

Sous le capot, les événements ne sont que des delegates avec des conventions d’appel spéciales. (Par exemple, vous n’avez pas à vérifier la nullité avant de déclencher un événement.)

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

Si l’événement a des auditeurs Appelez chaque écouteur de manière synchrone sur ce thread dans un ordre arbitraire.

Comme les événements sont multicast, ils auront zéro ou plusieurs écouteurs, contenus dans une collection. Le CLR les parcourra en boucle, appelant chacun dans un ordre arbitraire.

Une grande mise en garde à retenir est que les gestionnaires d’événements s’exécutent dans le même thread que l’événement est déclenché. C’est une erreur mentale courante de les considérer comme un nouveau thread. Ils ne.