son que programacion mas los llamar invocar eventos evento event delegados control comunes c# event-handling extension-methods static-methods

c# - que - Cancelar suscripción del controlador de eventos anónimos dentro de un método estático(método de extensión)



que es un evento de un control (3)

Tengo un método de extensión para suscribir un evento PropertyChanged de un objeto que implementa INotifyPropertyChanged .

Me gustaría que el evento se dispare solo una vez. No mas.

Este es mi método.

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) { if (target == null) { return; } PropertyChangedEventHandler handler = (obj, e) => { if (propertyName == e.PropertyName) { action(); } }; target.PropertyChanged -= handler; target.PropertyChanged += handler; }

Pero no funciona. No puedo eliminar el controlador de eventos para que el evento se active cada vez que llamo a este método.

He intentado con un enfoque diferente. En lugar de usar métodos anónimos, algo más tradicional, como este:

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) { if (target == null) { return; } target.PropertyChanged -= target_PropertyChanged; target.PropertyChanged += target_PropertyChanged; } static void target_PropertyChanged(object sender, PropertyChangedEventArgs e) { //do stuff here }

Y simplemente funciona bien. El evento se dispara solo una vez, pero también necesito el parámetro Acción. No puedo usarlo con este enfoque.

¿Alguna solución alternativa o enfoque diferente para resolver este problema? ¿Hay algo extraño con los métodos anónimos dentro de los métodos estáticos?

Gracias por adelantado.


Puede obtener su primer ejemplo para que funcione si anula su suscripción desde el controlador de eventos.

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) { if (target == null) { return; } // Declare the handler first, in order to create // a concrete reference that you can use from within // the delegate PropertyChangedEventHandler handler = null; handler = (obj, e) => { if (propertyName == e.PropertyName) { obj.PropertyChanged -= handler; //un-register yourself action(); } }; target.PropertyChanged += handler; }

El código anterior sirve como controlador de eventos "uno y hecho". Puede registrar un número ilimitado de estos, y cada uno solo se ejecutará una vez antes de anular el registro.

Tenga en cuenta que es posible hacer que uno de estos manejadores se ejecute varias veces, si levanta el evento a través de múltiples hilos en una secuencia corta. Para evitar esto, es posible que necesite crear instancias de objetos de mapeo de Diccionario estático (T, T) para "bloquear objetos" y agregar algún código de centinela para garantizar que un controlador solo se ejecute una vez. Sin embargo, esos detalles de implementación parecen estar un poco fuera del alcance de su pregunta tal como está escrita actualmente.


Técnicamente, no es el mismo método anónimo que intentas cancelar. .NET crea una nueva instancia de ese método cada vez que se llama a OnPropertyChanged . Es por eso que cancelar la suscripción no funcionará.


Esa es una limitación del uso de métodos anónimos como controladores de eventos. No se pueden eliminar como lo haría con un método normal (que técnicamente es una instancia de delegado que se crea automáticamente mediante una conversión de grupo de métodos) porque los métodos anónimos se compilan en una clase de contenedor generada por el compilador y se crea una nueva instancia de la clase cada vez.

Para preservar el parámetro de acción, podría crear una clase contenedora que tendría el delegado para su controlador de eventos dentro. La clase puede declararse privada dentro de la otra clase con la que está trabajando, o puede hacerse interna, tal vez en un espacio de nombres de "Ayudantes". Se vería algo como esto:

class DelegateContainer { public DelegateContainer(Action theAction, string propName) { TheAction = theAction; PopertyName = propName; } public Action TheAction { get; private set; } public string PropertyName { get; private set; } public void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) { if(PropertyName == e.PropertyName) TheAction(); } }

Luego, crea y almacena la referencia al contenedor en tu clase. Puede crear un miembro currentContainer y luego establecer el controlador de esta manera:

private static DelegateContainer currentContainer; public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) { if (target == null) { return; } if(currentContainer != null) target.PropertyChanged -= currentContainer.PropertyChangedHandler; currentContainer = new DelegateContainer(action, propertyName); target.PropertyChanged += currentContainer.PropertyChangedHandler; }