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 :)

È stato utile?

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à sempre null 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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top