Unsubscribe método anónimo en C#


¿Es posible cancelar la suscripción de un método anónimo a un evento?

Si me suscribo a un evento como este:

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

MyEvent += MyMethod;

Puedo cancelar la suscripción así:

MyEvent -= MyMethod;

Pero si me suscribo usando un método anónimo:

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

¿Es posible cancelar la suscripción a este método anónimo? Si es así, ¿cómo?

Author: Eric, 2008-10-08

11 answers

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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Simplemente mantenga una referencia al delegado alrededor.

 203
Author: Jacob Krall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-13 19:50:55

Una técnica es declarar una variable para mantener el método anónimo que entonces estaría disponible dentro del propio método anónimo. Esto funcionó para mí porque el comportamiento deseado era cancelar la suscripción después de que se manejara el evento.

Ejemplo:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;
 137
Author: J c,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-08 15:34:51

Desde la memoria, la especificación explícitamente no garantiza el comportamiento de ninguna manera cuando se trata de la equivalencia de delegados creados con métodos anónimos.

Si necesita darse de baja, debe usar un método "normal" o conservar el delegado en otro lugar para que pueda darse de baja exactamente con el mismo delegado que utilizó para suscribirse.

 18
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-08 15:26:56

En 3.0 se puede acortar a:

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;
 17
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-23 01:53:51

En lugar de mantener una referencia a cualquier delegado, puede instrumentar su clase para devolver la lista de invocación del evento al llamante. Básicamente puedes escribir algo como esto (asumiendo que MyEvent es declarado dentro de MyClass):

public class MyClass 
{
  public event EventHandler MyEvent;

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

Para que pueda acceder a toda la lista de invocaciones desde fuera de MyClass y cancelar la suscripción a cualquier controlador que desee. Por ejemplo:

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

He escrito un post completo sobre esta técnica aquí.

 9
Author: hemme,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-23 23:11:50

Tipo de enfoque cojo:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Invalida los métodos de evento add/remove.
  2. Mantenga una lista de esos controladores de eventos.
  3. Cuando sea necesario, límpielos todos y vuelva a agregar los demás.

Esto puede no funcionar o ser el método más eficiente, pero debe hacer el trabajo.

 6
Author: casademora,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-08 15:37:24

Desde que C# 7.0 funciones locales se ha liberado la característica, el enfoque sugerido por jc se vuelve realmente limpio.

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

Así que, honestamente, no tienes una función anónima como variable aquí. Pero supongo que la motivación para usarlo en su caso se puede aplicar a las funciones locales.

 3
Author: mazharenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-15 05:30:06

Si desea poder controlar la cancelación de suscripción, debe seguir la ruta indicada en su respuesta aceptada. Sin embargo, si solo le preocupa aclarar las referencias cuando su clase de suscripción se sale de su alcance, entonces hay otra solución (ligeramente complicada) que implica usar referencias débiles. Acabo de publicar una pregunta y respuesta sobre este tema.

 2
Author: Benjol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:18

Una solución simple:

Simplemente pase la variable eventhandle como parámetro a sí misma. Evento si tiene el caso de que no puede acceder a la variable creada original debido a multihilo, puede usar esto:

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!");
}
 1
Author: Manuel Marhold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-12 14:11:21

Si desea referirse a algún objeto con este delegado, puede ser que pueda usar Delegado.CreateDelegate (Type, Object target, MethodInfo MethodInfo) . net considera que el delegado es igual por target y MethodInfo

 0
Author: user3217549,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-21 03:19:30

Si la mejor manera es mantener una referencia en el EventHandler suscrito, esto se puede lograr usando un diccionario.

En este ejemplo, tengo que usar un método anónimo para incluir el parámetro mergeColumn para un conjunto de DataGridViews.

Usar el método MergeColumn con el parámetro enable establecido en true habilita el evento mientras que usarlo con false lo deshabilita.

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);
        }
    }
}
 0
Author: Larry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-13 06:40:33