¿Cómo funcionan los eventos C # detrás de escena?

Estoy usando C #, .NET 3.5. Entiendo cómo utilizar eventos, cómo declararlos en mi clase, cómo engancharlos en otro lugar, etc. Un ejemplo artificial:

public class MyList { private List m_Strings = new List(); public EventHandler 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(Fired); tmp.Add("test"); Assert.IsTrue(m_Fired); } private void Fired(object sender, EventArgs args) { m_Fired = true; } } 

Sin embargo, lo que no entiendo, es cuando uno declara un controlador de eventos

 public EventHandler ElementAddedEvent; 

Nunca se inicializa, entonces, ¿qué es exactamente ElementAddedEvent? ¿A qué apunta? Lo siguiente no funcionará, porque EventHandler nunca se inicializa:

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

Observé que hay un EventHandler.CreateDelegate (…), pero todas las firmas de método sugieren que esto solo se usa para unir delegates a un EventHandler ya existente a través del ElementAddedEvent + típico = new EventHandler (MyMethod).

No estoy seguro de si lo que estoy tratando de hacer ayudará … pero en última instancia me gustaría encontrar un padre abstracto DataContext en LINQ cuyos hijos puedan registrar qué tabla de tipos desean “observar” para que pueda tener eventos como BeforeUpdate y AfterUpdate, pero específico para los tipos. Algo como esto:

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

Pensar en esto me hizo darme cuenta de que realmente no entiendo lo que está pasando debajo de la hod con los eventos, y me gustaría entender 🙂

Lo escribí con bastante detalle en un artículo , pero aquí está el resumen, suponiendo que esté razonablemente satisfecho con los delegates :

  • Un evento es solo un método de “agregar” y un método de “eliminar”, de la misma manera que una propiedad es realmente solo un método de “obtener” y un método de “configuración”. (De hecho, la CLI también permite un método de “subir / disparar”, pero C # nunca lo genera). Los metadatos describen el evento con referencias a los métodos.
  • Cuando declara un evento parecido a un campo (como su ElementAddedEvent), el comstackdor genera los métodos y un campo privado (del mismo tipo que el delegado). Dentro de la clase, cuando te refieres a ElementAddedEvent te refieres al campo. Fuera de la clase, te estás refiriendo al campo.
  • Cuando alguien se suscribe a un evento (con el operador + =) que llama al método add. Cuando cancelan la suscripción (con el operador – =) que llama a la eliminación.
  • Para los eventos de tipo campo, hay algo de sincronización, pero de lo contrario el complemento / eliminar simplemente llama Delegate. Combinar / Eliminar para cambiar el valor del campo generado automáticamente. Ambas operaciones se asignan al campo de respaldo; recuerde que los delegates son inmutables. En otras palabras, el código autogenerado es muy parecido a esto:

     // 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); } } } 
  • El valor inicial del campo generado en su caso es null , y siempre volverá a ser null si se eliminan todos los suscriptores, ya que ese es el comportamiento de Delegate.Remove.

  • Si desea que un manejador “no operado” se suscriba a su evento, para evitar la verificación de nulidad, puede hacer:

     public EventHandler ElementAddedEvent = delegate {}; 

    El delegate {} es solo un método anónimo que no se preocupa por sus parámetros y no hace nada.

Si hay algo que aún no está claro, por favor pregunte e intentaré ayudarlo.

Bajo el capó, los eventos son solo delegates con convenciones de llamadas especiales. (Por ejemplo, no es necesario verificar la nulidad antes de generar un evento).

En pseudocódigo, Event.Invoke () se divide de esta manera:

Si el evento tiene oyentes llame a cada oyente sincrónicamente en este hilo en orden arbitrario.

Como los eventos son multidifusión, tendrán cero o más oyentes, que se guardarán en una colección. El CLR recorrerá a través de ellos, llamando a cada uno en un orden arbitrario.

Una gran advertencia para recordar es que los manejadores de eventos se ejecutan en el mismo hilo en el que se genera el evento. Es un error mental común pensar en ellos como un nuevo hilo. Ellos no.