Désinscription de la méthode anonyme en C #


222

Est-il possible de désinscrire une méthode anonyme d'un événement?

Si je m'abonne à un événement comme celui-ci:

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

MyEvent += MyMethod;

Je peux me désinscrire comme ceci:

MyEvent -= MyMethod;

Mais si je m'abonne en utilisant une méthode anonyme:

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

est-il possible de se désinscrire de cette méthode anonyme? Si c'est le cas, comment?


4
Quant à savoir pourquoi vous ne pouvez pas faire cela: stackoverflow.com/a/25564492/23354
Marc Gravell

Réponses:


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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Gardez simplement une référence au délégué autour.


141

Une technique consiste à déclarer une variable pour contenir la méthode anonyme qui serait alors disponible à l'intérieur de la méthode anonyme elle-même. Cela a fonctionné pour moi car le comportement souhaité était de se désinscrire après la gestion de l'événement.

Exemple:

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

1
En utilisant ce type de code, Resharper se plaint d'accéder à une fermeture modifiée ... cette approche est-elle fiable? Je veux dire, sommes-nous sûrs que la variable 'foo' à l'intérieur du corps de la méthode anonyme, fait vraiment référence à la méthode anonyme elle-même?
BladeWise

7
J'ai trouvé une réponse à mon doute, et c'est que 'foo' tiendra vraiment une référence à la méthode anonyme itslef. La variable capturée est modifiée, car elle est capturée avant que la méthode anonyme ne lui soit affectée.
BladeWise

2
C'est exactement ce dont j'avais besoin! Je manquais le = null. (MyEventHandler foo = delegate {... MyEvent- = foo;}; MyEvent + = foo; n'a pas fonctionné ...)
TDaver

Resharper 6.1 ne se plaint pas si vous le déclarez en tant que tableau. Cela semble un peu bizarre, mais je vais faire aveuglément confiance à mes outils sur celui-ci: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Mike Post

21

De mémoire, la spécification ne garantit pas explicitement le comportement dans les deux cas lorsqu'il s'agit d'équivalence de délégués créés avec des méthodes anonymes.

Si vous devez vous désinscrire, vous devez soit utiliser une méthode "normale", soit conserver le délégué ailleurs afin de pouvoir vous désinscrire avec exactement le même délégué que vous avez utilisé pour vous abonner.


Je Jon, que me dites-vous? Je ne comprends pas. la solution exposée par "J c" ne fonctionnera pas correctement?
Eric Ouellet

@EricOuellet: Cette réponse est fondamentalement une implémentation de "conserver le délégué ailleurs afin que vous puissiez vous désinscrire avec exactement le même délégué que vous avez utilisé pour vous abonner".
Jon Skeet

Jon, je suis désolé, j'ai lu votre réponse plusieurs fois en essayant de comprendre ce que vous voulez dire et où la solution "J c" n'utilise pas le même délégué pour vous abonner et vous désabonner, mais je ne peux pas le comprendre. Peut-être pouvez-vous m'indiquer un article qui explique ce que vous dites? Je connais votre réputation et j'aimerais vraiment comprendre ce que vous voulez dire, tout ce que vous pouvez lier serait vraiment apprécié.
Eric Ouellet

1
J'ai trouvé: msdn.microsoft.com/en-us/library/ms366768.aspx mais ils recommandent de ne pas utiliser anonyme mais ils ne disent pas qu'il y a un problème majeur?
Eric Ouellet

Je l'ai trouvé ... Merci beaucoup (voir la réponse de Michael Blome): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Eric Ouellet

16

En 3.0 peut être raccourci à:

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

15

Depuis que la fonction des fonctions locales C # 7.0 a été publiée, l'approche suggérée par J c devient vraiment soignée.

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

Donc, honnêtement, vous n'avez pas de fonction anonyme comme variable ici. Mais je suppose que la motivation pour l'utiliser dans votre cas peut s'appliquer aux fonctions locales.


1
Pour rendre la lisibilité encore meilleure, vous pouvez déplacer le MyEvent + = foo; ligne avant la déclaration de foo.
Mark Zhukovsky

9

Au lieu de conserver une référence à un délégué, vous pouvez instrumenter votre classe afin de rendre la liste d'invocation de l'événement à l'appelant. Fondamentalement, vous pouvez écrire quelque chose comme ça (en supposant que MyEvent est déclaré dans MyClass):

public class MyClass 
{
  public event EventHandler MyEvent;

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

Ainsi, vous pouvez accéder à la liste d'invocation entière depuis l'extérieur de MyClass et vous désinscrire de tout gestionnaire que vous souhaitez. Par exemple:

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

J'ai écrit un article complet sur cette technique ici .


2
Cela signifie-t-il que je pourrais accidentellement me désinscrire d'une instance différente (c'est-à-dire pas moi) de l'événement s'ils se sont abonnés après moi?
dumbledad

@dumbledad bien sûr, cela annulerait toujours le dernier enregistrement. Si vous souhaitez désinscrire dynamiquement un délégué anonyme spécifique, vous devez l'identifier d'une manière ou d'une autre. Je suggère de garder une référence alors :)
LuckyLikey

C'est assez cool, ce que vous faites, mais je ne peux pas imaginer un cas où cela pourrait être utile. Mais je ne résout pas vraiment la question du PO. -> +1. À mon humble avis, il ne faut tout simplement pas utiliser des délégués anonymes s'ils devaient être radiés ultérieurement. Les garder est stupide -> meilleure méthode d'utilisation. Supprimer juste un délégué dans la liste d'invocation est assez aléatoire et inutile. Corrige moi si je me trompe. :)
LuckyLikey

6

Type d'approche boiteuse:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Remplacez les méthodes d'ajout / suppression d'événement.
  2. Gardez une liste de ces gestionnaires d'événements.
  3. Au besoin, effacez-les tous et rajoutez les autres.

Cela peut ne pas fonctionner ou être la méthode la plus efficace, mais devrait faire le travail.


14
Si vous pensez que c'est boiteux, ne le postez pas.
Jerry Nixon

2

Si vous souhaitez pouvoir contrôler la désinscription, vous devez suivre l'itinéraire indiqué dans votre réponse acceptée. Cependant, si vous souhaitez simplement effacer les références lorsque votre classe d'abonnement est hors de portée, il existe une autre solution (légèrement compliquée) qui implique l'utilisation de références faibles. Je viens de poster une question et une réponse sur ce sujet.


2

Une solution simple:

transmettez-lui simplement la variable eventhandle comme paramètre. Si vous avez le cas où vous ne pouvez pas accéder à la variable créée d'origine en raison du multithreading, vous pouvez utiliser ceci:

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!");
}

que faire si MyEvent est appelé deux fois, avant d' MyEvent -= myEventHandlerInstance;être exécuté? Si c'est possible, vous auriez une erreur. Mais je ne sais pas si c'est le cas.
LuckyLikey

0

si vous voulez faire référence à un objet avec ce délégué, vous pouvez peut-être utiliser Delegate.CreateDelegate (Type, Object target, MethodInfo methodInfo) .net considérez le délégué égal par cible et methodInfo


0

Si le meilleur moyen est de conserver une référence sur le gestionnaire d'événements auquel vous êtes abonné, vous pouvez le faire à l'aide d'un dictionnaire.

Dans cet exemple, je dois utiliser une méthode anonyme pour inclure le paramètre mergeColumn pour un ensemble de DataGridViews.

L'utilisation de la méthode MergeColumn avec le paramètre enable défini sur true active l'événement tout en l'utilisant avec false le désactive.

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);
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.