Annulla l'iscrizione al metodo anonimo in C #


222

È possibile annullare l'iscrizione di un metodo anonimo a un evento?

Se mi iscrivo a un evento come questo:

void MyMethod()
{
    Console.WriteLine("I did it!");
}

MyEvent += MyMethod;

Posso annullare l'iscrizione in questo modo:

MyEvent -= MyMethod;

Ma se mi iscrivo usando un metodo anonimo:

MyEvent += delegate(){Console.WriteLine("I did it!");};

è possibile annullare l'iscrizione a questo metodo anonimo? Se é cosi, come?


4
Per quanto riguarda il motivo per cui non si può fare questo: stackoverflow.com/a/25564492/23354
Marc Gravell

Risposte:


230
Action myDelegate = delegate(){Console.WriteLine("I did it!");};

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Basta tenere un riferimento al delegato in giro.


141

Una tecnica consiste nel dichiarare una variabile per contenere il metodo anonimo che sarebbe quindi disponibile all'interno del metodo anonimo stesso. Questo ha funzionato per me perché il comportamento desiderato era l'annullamento dell'iscrizione dopo la gestione dell'evento.

Esempio:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;

1
Utilizzando questo tipo di codice, Resharper si lamenta dell'accesso a una chiusura modificata ... questo approccio è affidabile? Voglio dire, siamo sicuri che la variabile "foo" all'interno del corpo del metodo anonimo si riferisca realmente al metodo anonimo stesso?
BladeWise,

7
Ho trovato una risposta al mio dubbio, ed è che "pippo" conterrà veramente un riferimento al metodo anonimo che ne deriva. La variabile acquisita viene modificata, poiché viene acquisita prima che il metodo anonimo venga assegnato ad essa.
BladeWise,

2
Questo è esattamente quello di cui avevo bisogno! Mi mancava il = null. (MyEventHandler foo = delegate {... MyEvent- = foo;}; MyEvent + = foo; non ha funzionato ...)
TDaver,

Resharper 6.1 non si lamenta se lo dichiari come un array. Sembra un po 'strano, ma mi fiderò ciecamente dei miei strumenti su questo: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Mike Post,

21

Dalla memoria, la specifica non garantisce esplicitamente il comportamento in entrambi i casi quando si tratta di equivalenza di delegati creati con metodi anonimi.

Se è necessario annullare l'iscrizione, è necessario utilizzare un metodo "normale" o conservare il delegato altrove in modo da poter annullare l'iscrizione esattamente con lo stesso delegato utilizzato per l'abbonamento.


Io Jon, cosa mi dici? Non capisco. la soluzione esposta da "J c" non funzionerà correttamente?
Eric Ouellet,

@EricOuellet: quella risposta è sostanzialmente un'implementazione di "conserva il delegato da qualche altra parte in modo da poter annullare l'iscrizione esattamente con lo stesso delegato che hai usato per iscriverti".
Jon Skeet,

Jon, mi dispiace, ho letto la tua risposta molte volte cercando di capire cosa intendi e dove la soluzione "J c" non utilizza lo stesso delegato per iscriversi e annullare l'iscrizione, ma non riesco a capirlo. Forse puoi indicarmi un articolo che spiega cosa stai dicendo? Conosco la tua reputazione e mi piacerebbe davvero capire cosa intendi, qualunque cosa tu possa collegare sarebbe davvero apprezzata.
Eric Ouellet,

1
Ho trovato: msdn.microsoft.com/en-us/library/ms366768.aspx ma loro raccomandano di non usare l'anonimo ma non dicono che c'è qualche grosso problema?
Eric Ouellet,

L'ho trovato ... Grazie mille (vedi risposta Michael Blome): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Eric Ouellet,

16

In 3.0 può essere abbreviato in:

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;

15

Poiché la funzione delle funzioni locali di C # 7.0 è stata rilasciata, l'approccio suggerito da J c diventa davvero pulito.

void foo(object s, MyEventArgs ev)
{
    Console.WriteLine("I did it!");
    MyEvent -= foo;
};
MyEvent += foo;

Quindi, onestamente, non hai una funzione anonima come variabile qui. Ma suppongo che la motivazione per usarlo nel tuo caso possa essere applicata alle funzioni locali.


1
Per migliorare ulteriormente la leggibilità, puoi spostare MyEvent + = foo; linea di essere prima della dichiarazione di foo.
Mark Zhukovsky,

9

Invece di mantenere un riferimento a qualsiasi delegato, puoi strumentare la tua classe per restituire al chiamante l'elenco di invocazione dell'evento. Fondamentalmente puoi scrivere qualcosa del genere (supponendo che MyEvent sia dichiarato all'interno di MyClass):

public class MyClass 
{
  public event EventHandler MyEvent;

  public IEnumerable<EventHandler> GetMyEventHandlers()  
  {  
      return from d in MyEvent.GetInvocationList()  
             select (EventHandler)d;  
  }  
}

Quindi puoi accedere all'intero elenco di invocazioni dall'esterno di MyClass e annullare l'iscrizione a qualsiasi gestore che desideri. Per esempio:

myClass.MyEvent -= myClass.GetMyEventHandlers().Last();

Ho scritto un post completo su questa tecnica qui .


2
Questo significa che potrei accidentalmente annullare l'iscrizione a un'istanza diversa (cioè non a me) dall'evento se si sono abbonati dopo di me?
silente il

@dumbledad ovviamente questo cancellerebbe sempre l'ultimo registrato. Se si desidera annullare la sottoscrizione dinamica di uno specifico delegato anonimo, è necessario in qualche modo identificarlo. Suggerirei di mantenere un riferimento quindi :)
LuckyLikey

È piuttosto bello, quello che stai facendo, ma non riesco a immaginare un caso in cui questo potrebbe essere utile. Ma non risolve davvero la domanda del PO. -> +1. IMHO, uno non dovrebbe semplicemente usare delegati anonimi se dovessero essere cancellati in seguito. Mantenerli è stupido -> usare meglio il Metodo. La rimozione di alcuni delegati nell'elenco Invocazioni è piuttosto casuale e inutile. Correggimi se sbaglio. :)
LuckyLikey,

6

Approccio tipo di zoppo:

public class SomeClass
{
  private readonly IList<Action> _eventList = new List<Action>();

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Sostituisci i metodi di aggiunta / rimozione dell'evento.
  2. Tenere un elenco di quei gestori di eventi.
  3. Se necessario, cancellali tutti e aggiungi nuovamente gli altri.

Questo potrebbe non funzionare o essere il metodo più efficiente, ma dovrebbe portare a termine il lavoro.


14
Se pensi che sia zoppo, non pubblicarlo.
Jerry Nixon,

2

Se vuoi essere in grado di controllare l'annullamento dell'iscrizione, devi seguire il percorso indicato nella risposta accettata. Tuttavia, se sei solo preoccupato di chiarire i riferimenti quando la tua classe di abbonamento esce dal campo di applicazione, esiste un'altra soluzione (leggermente contorta) che prevede l'utilizzo di riferimenti deboli. Ho appena pubblicato una domanda e una risposta su questo argomento.


2

Una soluzione semplice:

passa la variabile eventhandle come parametro a se stessa. Evento se si ha il caso in cui non è possibile accedere alla variabile creata originale a causa del multithreading, è possibile utilizzare questo:

MyEventHandler foo = null;
foo = (s, ev, mehi) => MyMethod(s, ev, foo);
MyEvent += foo;

void MyMethod(object s, MyEventArgs ev, MyEventHandler myEventHandlerInstance)
{
    MyEvent -= myEventHandlerInstance;
    Console.WriteLine("I did it!");
}

cosa succede se MyEvent viene richiamato due volte, prima che MyEvent -= myEventHandlerInstance;venga eseguito? Se è possibile, avresti un errore. Ma non sono sicuro che sia così.
LuckyLikey,

0

se si desidera fare riferimento a un oggetto con questo delegato, è possibile utilizzare Delegate.CreateDelegate (Type, Object target, MethodInfo methodInfo) .net considerare il delegato uguale a target e methodInfo


0

Se il modo migliore è mantenere un riferimento sull'eventoHandler sottoscritto, questo può essere ottenuto usando un Dizionario.

In questo esempio, devo usare un metodo anonimo per includere il parametro mergeColumn per un set di DataGridViews.

L'uso del metodo MergeColumn con il parametro enable impostato su true abilita l'evento mentre lo utilizza con false lo disabilita.

static Dictionary<DataGridView, PaintEventHandler> subscriptions = new Dictionary<DataGridView, PaintEventHandler>();

public static void MergeColumns(this DataGridView dg, bool enable, params ColumnGroup[] mergedColumns) {

    if(enable) {
        subscriptions[dg] = (s, e) => Dg_Paint(s, e, mergedColumns);
        dg.Paint += subscriptions[dg];
    }
    else {
        if(subscriptions.ContainsKey(dg)) {
            dg.Paint -= subscriptions[dg];
            subscriptions.Remove(dg);
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.