2013-05-24 9 views
8

Quello che voglio fare è sostanzialmente rimuovere una funzione da un evento, senza conoscere il nome della funzione.Come rimuovere te stesso da un gestore di eventi?

Ho un FileSystemWatcher. Se un file viene creato/rinominato, ne verifica il nome. Se corrisponde, quindi lo sposta in una posizione specifica. Tuttavia, se il file è bloccato, crea un lambda che si collega a un evento di tick del timer, in attesa che il file non sia bloccato. Quando non lo è, sposta il file e quindi si rimuove dal gestore eventi. Ho visto molti modi per farlo, come mantenere l'istanza o fare un metodo con nome. Non posso fare nessuno dei due qui. Quali sono le mie opzioni?

+0

C'è un paio di modelli là fuori che ti permettono di sottoscrivere gli eventi in un * Loosley accoppiato * modo. Dai un'occhiata a [questo] (http://martinfowler.com/eaaDev/EventAggregator.html). Descrive come iscriversi/annullare l'iscrizione a un aggregatore di eventi (in questo caso si tratta di schermate). L'aggregatore effettivo crea i riferimenti tra gli oggetti. Ci sono molte implementazioni differenti là fuori. Quello che preferisco è implementato in [Caliburn.Micro] (http://www.mindscapehq.com/blog/index.php/2012/02/01/caliburn-micro-part-4-the-event-aggregator/) . –

risposta

19

Non esiste un metodo semplice per raggiungere questo obiettivo.

approccio preferito:

non vedo il motivo per cui non è possibile salvare il delegato. Non è necessario salvare l'istanza come un campo. Può essere una variabile locale che viene catturata dal vostro gestore di eventi anonimo:

EventHandler<TypeOfEventArgs> handler = null; 
handler = (s, e) => 
{ 
    // Do whatever you need to do here 

    // Remove event: 
    foo.Event -= handler; 
} 

foo.Event += handler; 

non riesco a pensare ad un unico scenario in cui non è possibile utilizzare questo.

approccio alternativo senza salvare il delegato:

Tuttavia, se si dispone di un tale scenario, è ottenere abbastanza complicato.
È necessario trovare il delegato che è stato aggiunto come gestore all'evento. Perché non l'hai salvato, è piuttosto difficile ottenerlo. Non c'è this per ottenere un delegato del metodo attualmente in esecuzione.

Non è possibile utilizzare GetInvocationList() sull'evento sia, perché l'accesso a un evento al di fuori della classe è definita in è limitato a aggiungere e rimuovere i gestori, vale a dire += e -=.

Impossibile creare un nuovo delegato. Mentre è possibile accedere all'oggetto MethodInfo che definisce il metodo anonimo, non è possibile accedere all'istanza della classe in cui è dichiarato il metodo. Questa classe viene generata automaticamente dal compilatore e chiamando lo this all'interno del metodo anonimo restituirà il istanza della classe in cui è definito il metodo normale.

L'unico modo in cui ho trovato che funziona è trovare il campo, se presente, che l'evento utilizza e chiamare GetInvocationList() su di esso. Il codice seguente illustra questo con una classe fittizia:

void Main() 
{ 
    var foo = new Foo(); 
    foo.Bar += (s, e) => { 
     Console.WriteLine("Executed"); 

     var self = new StackFrame().GetMethod(); 
     var eventField = foo.GetType() 
          .GetField("Bar", BindingFlags.NonPublic | 
              BindingFlags.Instance); 
     if(eventField == null) 
      return; 
     var eventValue = eventField.GetValue(foo) as EventHandler; 
     if(eventValue == null) 
      return; 
     var eventHandler = eventValue.GetInvocationList() 
            .OfType<EventHandler>() 
            .FirstOrDefault(x => x.Method == self) 
           as EventHandler; 
     if(eventHandler != null) 
      foo.Bar -= eventHandler; 
    }; 

    foo.RaiseBar(); 
    foo.RaiseBar(); 
} 

public class Foo 
{ 
    public event EventHandler Bar; 
    public void RaiseBar() 
    { 
     var handler = Bar; 
     if(handler != null) 
      handler(this, EventArgs.Empty); 
    } 
} 

Si prega di notare che la stringa "Bar" che viene passata al GetField deve essere il nome esatto del campo che viene utilizzato dall'evento. Ciò comporta due problemi:

  1. Il campo può essere denominato diversamente, ad es. quando si utilizza un'implementazione di un evento esplicito. Devi trovare manualmente il nome del campo.
  2. Potrebbe non esserci alcun campo. Ciò accade se l'evento utilizza un'implementazione di un evento esplicito e si limita a delegare a un altro evento o a memorizzare i delegati in qualche altro modo.

Conclusione:

L'approccio alternativo si basa su dettagli di implementazione, in modo da non utilizzare, se si può evitare.

+0

Ho dimenticato le chiusure! Ops! Grazie per la risposta però, sei andato ancora più lontano di me. –

+0

Penso che sia un po 'ingiusto che tu abbia scritto un commento negativo sulla risposta di Dark Falcon che ha preceduto il tuo, quindi hai dato essenzialmente lo stesso consiglio nel tuo approccio * preferito *. –

+0

@ BenVoigt Bene, il suo commento era giusto, tecnicamente non è una risposta, anche se se è possibile è la soluzione preferita. Si noti inoltre che non ha minimizzato la risposta di Dark, ma ha semplicemente commentato dicendo che tecnicamente non soddisfa i criteri indicati. Un'altra differenza fondamentale è che Daniel ha incluso una soluzione che * soddisfa * i criteri nella domanda. – Servy

0

procedura per rimuovere gestore di eventi con l'espressione lambda:

public partial class Form1 : Form 
{ 
    private dynamic myEventHandler; 
    public Form1() 
    { 
     InitializeComponent(); 
    } 
    private void Form1_Load(object sender, EventArgs e) 
    { 
     myEventHandler = new System.EventHandler((sender2, e2) => this.button1_Click(sender, e, "Hi there")); 
     this.button1.Click += myEventHandler; 
    } 

    private void button1_Click(object sender, EventArgs e, string additionalInfo) 
    { 
     MessageBox.Show(additionalInfo); 
     button1.Click -= myEventHandler; 
    } 
}