2009-09-07 11 views
17

ho il seguente codice:C# - funzioni anonime e gestori di eventi

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
{ 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    this.FoundStep += delegate(object sender, WalkerStepEventArgs e) 
         { 
         if (e.Step.ResourceType == res) retval.Add(e.Step); 
         }; 
    this.Start(); 
    return retval; 
} 

Notate come posso registrare il mio membro evento (FoundStep) per funzione locale sul posto anonimo.

La mia domanda è: quando termina la funzione 'FindStepByType' - la funzione anonima verrà rimossa automaticamente dalla lista dei delegati dell'evento o devo rimuoverla manualmente prima di uscire dalla funzione? (e come faccio?)

Spero che la mia domanda sia stata chiara.

risposta

34

il codice ha alcuni problemi (alcuni si e altri hanno identificato):

  • Il delegato anonima non può essere rimosso dalla manifestazione, come codificato.
  • Il delegato anonimo vivrà più a lungo della durata del metodo chiamandolo perché lo hai aggiunto a FoundStep che è un membro di questo.
  • Ogni voce in FindStepsByType aggiunge un altro delegato anonimo a FoundStep.
  • Il delegato anonimo è una chiusura ed efficacemente estende la durata della retval, quindi, anche se si smette di riferimento retval altrove nel codice, è comunque tenuto dal delegato anonimo.

Per risolvere questo problema, e usano ancora un delegato anonimo, assegnarlo a una variabile locale, e quindi rimuovere il gestore all'interno di un blocco finalmente (necessaria nel caso in cui il conduttore lancia un'eccezione):

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>(); 
    EventHandler<WalkerStepEventArgs> handler = (sender, e) => 
    { 
     if (e.Step.ResourceType == res) retval.Add(e.Step); 
    }; 

    this.FoundStep += handler; 

    try 
    { 
     this.Start(); 
    } 
    finally 
    { 
     this.FoundStep -= handler; 
    } 

    return retval; 
    } 

con C# 7.0 e versioni successive è possibile sostituire il delegato anonimo con una funzione locale, ottenendo lo stesso effetto:

public List<IWFResourceInstance> FindStepsByType(IWFResource res) 
    { 
     var retval = new List<IWFResourceInstance>(); 

     void Handler(object sender, WalkerStepEventArgs e) 
     { 
      if (e.Step.ResourceType == res) retval.Add(e.Step); 
     } 

     FoundStep += Handler; 

     try 
     { 
      this.Start(); 
     } 
     finally 
     { 
      FoundStep -= Handler; 
     } 

     return retval; 
    } 
5

No, non verrà rimosso automaticamente. In questo senso, non c'è differenza tra un metodo anonimo e un metodo "normale". Se lo desideri, dovresti annullare l'iscrizione manualmente all'evento.

In realtà, acquisirà altre variabili (ad esempio res nell'esempio) e le manterrà in vita (evita che i garbage collector le raccolgano).

+0

Non è lo stesso che usare i predicati? Quando uso i predicati non libero il delegato del predicato. –

+2

I predicati non vengono salvati da nessuna parte, ma qui, ci si iscrive a un evento. Finché l'oggetto che contiene l'evento è vivo, manterrà un riferimento al delegato e indirettamente alle sue variabili. Quando passi, ad esempio, '.Where (x => x.Hidden)', il metodo farà il lavoro con esso e lo getterà via (è solo una variabile locale per quanto riguarda il metodo 'Where'. Questo non è vero per il tuo caso, inoltre, se "Dove" è stato memorizzato da qualche parte, dovresti esserti preoccupato anche di questo. –

3

Quando si utilizza un delegato anonimo (o un'espressione lambda) per iscriversi a un evento non è possibile annullare facilmente l'iscrizione in seguito. Un gestore di eventi non viene mai automaticamente annullato.

Se si guarda il codice, anche se si dichiara e si abbona all'evento in una funzione, l'evento a cui si è iscritti è nella classe, quindi una volta sottoscritto verrà sempre sottoscritto anche dopo l'uscita dalla funzione. L'altra cosa importante da comprendere è che ogni volta che viene chiamata questa funzione, si iscriverà nuovamente all'evento. Questo è perfettamente legale poiché gli eventi sono essenzialmente delegati multicast e consentono più abbonati. (Questo potrebbe essere o non essere ciò che intendete.)

Per annullare l'iscrizione al delegato prima di uscire dalla funzione, è necessario memorizzare il delegato anonimo in una variabile delegata e aggiungere il delegato all'evento. Dovresti quindi essere in grado di rimuovere il delegato dall'evento prima che la funzione venga chiusa.

Per questi motivi, se è necessario annullare l'iscrizione dall'evento in un secondo momento, si sconsiglia di utilizzare delegati anonimi. Vedi How to: Subscribe to and Unsubscribe from Events (C# Programming Guide) (in particolare la sezione intitolata "Per iscriversi agli eventi utilizzando un metodo anonimo").

5

Qui di seguito è l'approccio su come evento unsubscribe in un metodo onymous:

DispatcherTimer _timer = new DispatcherTimer(); 
_timer.Interval = TimeSpan.FromMilliseconds(1000); 
EventHandler handler = null; 

int i = 0; 

_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev) 
{ 
    i++; 
    if(i==10) 
     _timer.Tick -= handler; 
}); 

_timer.Start();