Deaktivieren Sie die anonyme Methode in C #

222

Ist es möglich, eine anonyme Methode von einem Ereignis abzumelden?

Wenn ich eine Veranstaltung wie diese abonniere:

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

MyEvent += MyMethod;

Ich kann mich wie folgt abmelden:

MyEvent -= MyMethod;

Aber wenn ich mich mit einer anonymen Methode anmelde:

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

Ist es möglich, diese anonyme Methode abzubestellen? Wenn das so ist, wie?

Eric
quelle
4
Was , warum Sie das nicht tun können: stackoverflow.com/a/25564492/23354
Marc GRA

Antworten:

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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Behalten Sie einfach einen Verweis auf den Delegierten in der Nähe.

Jacob Krall
quelle
141

Eine Technik besteht darin, eine Variable zu deklarieren, die die anonyme Methode enthält, die dann in der anonymen Methode selbst verfügbar wäre. Dies funktionierte für mich, da das gewünschte Verhalten darin bestand, sich abzumelden, nachdem das Ereignis behandelt wurde.

Beispiel:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;
J c
quelle
1
Mit dieser Art von Code beschwert sich Resharper über den Zugriff auf einen modifizierten Verschluss. Ist dieser Ansatz zuverlässig? Ich meine, sind wir sicher, dass die Variable 'foo' im Hauptteil der anonymen Methode wirklich auf die anonyme Methode selbst verweist?
BladeWise
7
Ich habe eine Antwort auf meinen Dubt gefunden, und es ist so, dass 'foo' wirklich einen Verweis auf die anonyme Methode itslef enthält. Die erfasste Variable wird geändert, da sie erfasst wird, bevor die anonyme Methode ihr zugewiesen wird.
BladeWise
2
Genau das brauchte ich! Mir fehlte die = null. (MyEventHandler foo = delegieren {... MyEvent- = foo;}; MyEvent + = foo; hat nicht funktioniert ...)
TDaver
Resharper 6.1 beschwert sich nicht, wenn Sie es als Array deklarieren. Scheint ein bisschen komisch, aber ich werde meinen Werkzeugen in diesem Fall blind vertrauen: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Mike Post
21

Aus dem Speicher garantiert die Spezifikation das Verhalten in Bezug auf die Äquivalenz von Delegaten, die mit anonymen Methoden erstellt wurden, in keiner Weise.

Wenn Sie sich abmelden müssen, sollten Sie entweder eine "normale" Methode verwenden oder den Delegaten an einer anderen Stelle behalten, damit Sie sich mit genau demselben Delegaten abmelden können, den Sie zum Abonnieren verwendet haben.

Jon Skeet
quelle
Ich Jon, was machst du? Ich verstehe nicht. funktioniert die durch "J c" belichtete Lösung nicht richtig?
Eric Ouellet
@EricOuellet: Diese Antwort ist im Grunde eine Implementierung von "Den Delegaten an einem anderen Ort behalten, damit Sie sich mit genau demselben Delegaten abmelden können, den Sie zum Abonnieren verwendet haben".
Jon Skeet
Jon, es tut mir leid, ich habe Ihre Antwort oft gelesen, um herauszufinden, was Sie meinen und wo die "J c" -Lösung nicht denselben Delegaten zum Abonnieren und Abbestellen verwendet, aber ich kann sie nicht finden. Vielleicht können Sie mich auf einen Artikel verweisen, der erklärt, was Sie sagen? Ich weiß um Ihren Ruf und würde wirklich gerne verstehen, was Sie meinen. Alles, worauf Sie verlinken können, wäre wirklich dankbar.
Eric Ouellet
1
Ich fand: msdn.microsoft.com/en-us/library/ms366768.aspx, aber sie empfehlen, nicht anonym zu verwenden, aber sie sagen nicht, dass es ein größeres Problem gibt?
Eric Ouellet
Ich habe es gefunden ... Vielen Dank (siehe Antwort von Michael Blome): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Eric Ouellet
16

In 3.0 kann verkürzt werden auf:

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

quelle
15

Da die Funktion für lokale Funktionen von C # 7.0 veröffentlicht wurde, wird der von J c vorgeschlagene Ansatz wirklich ordentlich.

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

Ehrlich gesagt haben Sie hier keine anonyme Funktion als Variable. Aber ich nehme an, die Motivation, es in Ihrem Fall zu verwenden, kann auf lokale Funktionen angewendet werden.

Mazharenko
quelle
1
Um die Lesbarkeit noch besser zu machen, können Sie MyEvent + = foo verschieben. Zeile vor der Erklärung von foo sein.
Mark Zhukovsky
9

Anstatt einen Verweis auf einen Delegierten zu behalten, können Sie Ihre Klasse instrumentieren, um die Aufrufliste des Ereignisses an den Anrufer zurückzugeben. Grundsätzlich können Sie so etwas schreiben (vorausgesetzt, MyEvent ist in MyClass deklariert):

public class MyClass 
{
  public event EventHandler MyEvent;

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

So können Sie von außerhalb von MyClass auf die gesamte Aufrufliste zugreifen und jeden gewünschten Handler abbestellen. Zum Beispiel:

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

Ich habe einen vollständigen Beitrag über dieses tecnique geschrieben hier .

Hemme
quelle
2
Bedeutet dies, dass ich versehentlich eine andere Instanz (dh nicht mich) als die Veranstaltung abbestellen könnte, wenn sie nach mir abonniert würde?
Dumbledad
@dumbledad natürlich würde dies immer den zuletzt registrierten abmelden. Wenn Sie einen bestimmten anonymen Delegaten dynamisch abbestellen möchten, müssen Sie ihn irgendwie identifizieren. Ich würde vorschlagen, dann eine Referenz zu behalten :)
LuckyLikey
Es ist ziemlich cool, was Sie tun, aber ich kann mir keinen Fall vorstellen, in dem dies nützlich sein könnte. Aber ich löse die Frage des OP nicht wirklich. -> +1. IMHO sollte man einfach keine anonymen Delegierten verwenden, wenn sie später abgemeldet werden sollen. Sie zu behalten ist dumm -> Methode besser anwenden. Das Entfernen nur eines Delegaten in der Aufrufliste ist ziemlich zufällig und nutzlos. Korrigiere mich, wenn ich falsch liege. :)
LuckyLikey
6

Art von lahmem Ansatz:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Überschreiben Sie die Methoden zum Hinzufügen / Entfernen von Ereignissen.
  2. Führen Sie eine Liste dieser Ereignishandler.
  3. Löschen Sie sie bei Bedarf alle und fügen Sie die anderen erneut hinzu.

Dies funktioniert möglicherweise nicht oder ist die effizienteste Methode, sollte aber die Arbeit erledigen.

casademora
quelle
14
Wenn du denkst, dass es lahm ist, poste es nicht.
Jerry Nixon
2

Wenn Sie die Abmeldung kontrollieren möchten, müssen Sie den in Ihrer akzeptierten Antwort angegebenen Weg gehen. Wenn Sie jedoch nur Bedenken haben, Referenzen zu löschen, wenn Ihre abonnierende Klasse den Gültigkeitsbereich verlässt, gibt es eine andere (leicht verschlungene) Lösung, bei der schwache Referenzen verwendet werden. Ich habe gerade eine Frage und Antwort zu diesem Thema gepostet .

Benjol
quelle
2

Eine einfache Lösung:

Übergeben Sie einfach die eventhandle-Variable als Parameter an sich selbst. Wenn Sie den Fall haben, dass Sie aufgrund von Multithreading nicht auf die ursprünglich erstellte Variable zugreifen können, können Sie Folgendes verwenden:

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!");
}
Manuel Marhold
quelle
Was ist, wenn MyEvent zweimal aufgerufen wird, bevor es ausgeführt MyEvent -= myEventHandlerInstance;wird? Wenn es möglich ist, haben Sie einen Fehler. Aber ich bin mir nicht sicher, ob das der Fall ist.
LuckyLikey
0

Wenn Sie mit diesem Delegaten auf ein Objekt verweisen möchten, können Sie möglicherweise Delegate.CreateDelegate (Typ, Objektziel, MethodInfo methodInfo) .net verwenden

user3217549
quelle
0

Wenn der beste Weg darin besteht, eine Referenz auf dem abonnierten eventHandler zu behalten, kann dies mithilfe eines Wörterbuchs erreicht werden.

In diesem Beispiel muss ich eine anonyme Methode verwenden, um den Parameter mergeColumn für eine Reihe von DataGridViews einzuschließen.

Die Verwendung der MergeColumn-Methode mit dem auf true gesetzten Parameter enable aktiviert das Ereignis, während die Verwendung mit false es deaktiviert.

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);
        }
    }
}
Larry
quelle