Come funzionano gli eventi C # dietro le quinte?
Domanda
Sto usando C #, .NET 3.5. Capisco come utilizzare gli eventi, come dichiararli nella mia classe, come agganciarli da qualche altra parte, ecc. Un esempio inventato:
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;
}
}
Tuttavia, ciò che faccio non è quando si dichiara un gestore di eventi
public EventHandler<EventArgs> ElementAddedEvent;
Non è mai inizializzato, quindi cos'è esattamente ElementAddedEvent? Cosa indica? Non funzionerà quanto segue, poiché EventHandler non è mai inizializzato:
[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;
}
}
Ho notato che esiste un EventHandler.CreateDelegate (...), ma tutte le firme del metodo suggeriscono che questo è usato solo per collegare i delegati a un EventHandler già esistente attraverso il tipico ElementAddedEvent + = new EventHandler (MyMethod).
Non sono sicuro che che cosa sto cercando di fare possa aiutare ... ma alla fine mi piacerebbe trovare un DataContext padre astratto in LINQ i cui figli possono registrare quali tipi di tabella vogliono "osservato" così posso avere eventi come BeforeUpdate e AfterUpdate, ma specifici per i tipi. Qualcosa del genere:
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
}
}
Pensare a questo mi ha fatto capire che non capisco davvero cosa sta succedendo sotto il controllo degli eventi - e vorrei capire :)
Soluzione
L'ho scritto in una discreta quantità di dettagli in un articolo , ma ecco il riassunto, supponendo che tu sia abbastanza soddisfatto degli delegati stessi :
- Un evento è solo un " aggiungi " metodo e un "rimuovi" metodo, allo stesso modo in cui una proprietà è in realtà solo un "get " metodo e un "set" metodo. (In effetti, la CLI consente anche un metodo "raise / fire", ma C # non lo genera mai.) I metadati descrivono l'evento con riferimenti ai metodi.
- Quando dichiari un evento simile a un campo (come ElementAddedEvent) il compilatore genera i metodi e un campo privato (dello stesso tipo del delegato). All'interno della classe, quando fai riferimento a ElementAddedEvent ti riferisci al campo. Fuori dalla classe, ti riferisci al campo.
- Quando qualcuno si iscrive a un evento (con l'operatore + =) che chiama il metodo add. Quando cancellano l'iscrizione (con l'operatore - =) che chiama il rimuovi.
-
Per eventi simili a campi, c'è una certa sincronizzazione, ma altrimenti l'aggiunta / rimozione chiama semplicemente Delegate. Combina / Rimuovi per modificare il valore del campo generato automaticamente. Entrambe queste operazioni vengono assegnate al campo di supporto: ricordare che i delegati sono immutabili. In altre parole, il codice generato automaticamente è molto simile a questo:
// 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); } } }
-
Il valore iniziale del campo generato nel tuo caso è
null
- e diventerà semprenull
di nuovo se tutti gli abbonati vengono rimossi, poiché questo è il comportamento di Delegate.Remove. -
Se vuoi un " no-op " gestore per iscriversi al tuo evento, in modo da evitare il controllo di nullità, puoi fare:
public EventHandler<EventArgs> ElementAddedEvent = delegate {};
Il
delegato {}
è solo un metodo anonimo a cui non interessano i suoi parametri e non fa nulla.
Se c'è qualcosa che non è ancora chiaro, chiedi e proverò ad aiutarti!
Altri suggerimenti
Sotto il cofano, gli eventi sono solo delegati con speciali convenzioni di chiamata. (Ad esempio, non è necessario verificare la nullità prima di generare un evento.)
In pseudocodice, Event.Invoke () si suddivide in questo modo:
Se l'evento ha ascoltatori Chiama ogni ascoltatore in modo sincrono su questo thread in ordine arbitrario.
Poiché gli eventi sono multicast, avranno zero o più listener, tenuti in una raccolta. Il CLR li attraverserà, chiamandoli ciascuno in un ordine arbitrario.
Un grande avvertimento da ricordare è che i gestori di eventi vengono eseguiti nello stesso thread in cui l'evento viene generato. È un errore mentale comune pensarli come generando un nuovo thread. Non lo fanno.