Modelo de controlador de eventos débil para uso con lambdas

De acuerdo, entonces esto es más una respuesta que una pregunta, pero después de hacer esta pregunta , y juntar los diversos fragmentos de Dustin Campbell , Egor , y también un último consejo del ‘ marco de IObservable / Rx / Reactive ‘, creo que He resuelto una solución viable para este problema en particular. Puede ser completamente reemplazado por el marco IObservable / Rx / Reactive, pero solo la experiencia lo mostrará.

He creado deliberadamente una nueva pregunta, para darme espacio para explicar cómo llegué a esta solución, ya que puede no ser inmediatamente obvia.

Hay muchas preguntas relacionadas, la mayoría te dice que no puedes usar lambdas en línea si deseas poder separarlas más tarde:

  • ¿Eventos débiles en .Net?
  • Desenganchar eventos con lambdas en C #
  • ¿El uso de lambdas como controladores de eventos causa una pérdida de memoria?
  • ¿Cómo darse de baja de un evento que usa una expresión lambda?
  • Darse de baja del método anónimo en C #

Y es cierto que si USTED desea poder separarlos más tarde, necesita mantener una referencia a su lambda. Sin embargo, si solo desea que el controlador de eventos se desconecte cuando su suscriptor se sale del scope, esta respuesta es para usted.

‘La respuesta

(Lea más abajo si quiere ver cómo llegué a esta solución)

Uso, dado un control con un evento vainilla MouseDown , y un evento EventHandler ValueEvent :

 // for 'vanilla' events SetAnyHandler( h => (o,e) => h(o,e), //don't ask me, but it works*. h => control.MouseDown += h, h => control.MouseDown -= h, subscriber, (s, e) => s.DoSomething(e)); //**See note below // for generic events SetAnyHandler( h => control.ValueEvent += h, h => control.ValueEvent -= h, subscriber, (s, e) => s.DoSomething(e)); //**See note below 

(* Esta es una solución de Rx )

(** es importante evitar invocar el objeto del suscriptor directamente aquí (por ejemplo, poner al suscriptor.DoSomething (e), o invocar DoSomething (e) directamente si estamos dentro de la clase del Suscriptor. Hacer esto de manera efectiva crea una referencia al suscriptor, que derrota por completo al objeto …)

Nota : en algunas circunstancias, esto PUEDE dejar referencias en las clases de envoltura creadas para las lambdas en memoria, pero solo pesan bytes, así que no me molesta demasiado.

Implementación:

 //This overload handles any type of EventHandler public static void SetAnyHandler( Func, TDelegate> converter, Action add, Action remove, S subscriber, Action action) where TArgs : EventArgs where TDelegate : class where S : class { var subs_weak_ref = new WeakReference(subscriber); TDelegate handler = null; handler = converter(new EventHandler( (s, e) => { var subs_strong_ref = subs_weak_ref.Target as S; if(subs_strong_ref != null) { action(subs_strong_ref, e); } else { remove(handler); handler = null; } })); add(handler); } // this overload is simplified for generic EventHandlers public static void SetAnyHandler( Action> add, Action> remove, S subscriber, Action action) where TArgs : EventArgs where S : class { SetAnyHandler, TArgs>( h => h, add, remove, subscriber, action); } 

El detalle

Mi punto de partida fue la excelente respuesta de Egor (ver enlace para la versión con comentarios):

 public static void Link(Publisher publisher, Control subscriber) { var subscriber_weak_ref = new WeakReference(subscriber); EventHandler> handler = null; handler = delegate(object sender, ValueEventArgs e) { var subscriber_strong_ref = subscriber_weak_ref.Target as Control; if (subscriber_strong_ref != null) subscriber_strong_ref.Enabled = e.Value; else { ((Publisher)sender).EnabledChanged -= handler; handler = null; } }; publisher.EnabledChanged += handler; } 

Lo que me molestó fue que el evento está codificado en el método. Entonces eso significa que para cada evento nuevo, hay un nuevo método para escribir.

Jugueteé y logré llegar a esta solución genérica:

 private static void SetAnyGenericHandler( Action> add, //to add event listener to publisher Action> remove, //to remove event listener from publisher S subscriber, //ref to subscriber (to pass to action) Action action) //called when event is raised where T : EventArgs where S : class { var subscriber_weak_ref = new WeakReference(subscriber); EventHandler handler = null; handler = delegate(object sender, T e) { var subscriber_strong_ref = subscriber_weak_ref.Target as S; if(subscriber_strong_ref != null) { Console.WriteLine("New event received by subscriber"); action(subscriber_strong_ref, e); } else { remove(handler); handler = null; } }; add(handler); } 

Sin embargo, el problema con esa solución es que SÓLO es genérica, no puede manejar las formas de triunfo estándar MouseUp, MouseDown, etc …

Así que traté de hacerlo aún más genérico:

 private static void SetAnyHandler( Action add, //to add event listener to publisher Action remove, //to remove event listener from publisher Subscriber subscriber, //ref to subscriber (to pass to action) Action action) where T : class { var subscriber_weak_ref = new WeakReference(subscriber); T handler = null; handler = delegate(object sender, R e) //<-compiler doesn't like this line { var subscriber_strong_ref = subscriber_weak_ref.Target as Subscriber; if(subscriber_strong_ref != null) { action(subscriber_strong_ref, e); } else { remove(handler); handler = null; } }; remove(handler); } 

Sin embargo, como insinué aquí , esto no comstackrá, porque no hay forma de obligar a T a ser un delegado.

En ese punto, casi me rindo. No tiene sentido tratar de luchar con las especificaciones de C #.

Sin embargo, ayer, descubrí el método Observable.FromEvent del marco Reactive, no tuve la implementación, pero el uso me pareció algo familiar y muy interesante:

 var mousedown = Observable.FromEvent( h => new MouseEventHandler(h), h => control.MouseDown += h, h => control.MouseDown -= h); 

Fue el primer argumento que llamó mi atención. Esta es la solución para la ausencia de una restricción de tipo de delegado. Lo tomamos pasando la función que creará el delegado.

Poner todo esto junto nos da la solución que se muestra en la parte superior de esta respuesta.

Idea tardía

Totalmente, me recomendé tomar el tiempo para aprender sobre el marco reactivo (o lo que sea que termine llamándose). Es MUY interesante, y un poco alucinante. Sospecho que también hará que las preguntas como esta sean totalmente redundantes.

Hasta ahora, lo más interesante que he visto han sido los videos en Channel9 .

Si te diriges a CodePlex hay un proyecto llamado Sharp Observation en el que el autor ha creado un buen proveedor de delegates débiles, implementado en MSIL. Rápido, flexible, fácil de usar: por ejemplo

 Action myDelegate = new Action( aMethodOnMyClass ); myDelegate.MakeWeak(); 

¡Tan fácil como eso!

He estado buscando una solución durante mucho tiempo y la mayoría usa una desagradable reflexión, pero la respuesta de Benjohl es genial. Lo he ajustado para agregar soporte para EventHandler no genérico, DependencyPropertyChangedEventArgs que no hereda de EventArgs y para permitirle anular el registro manualmente de un evento. Me interesarían mucho los pensamientos de las personas, especialmente Benjohl.

 ///  /// Weakly registers for events using . ///  public sealed class WeakEvent { private Action removeEventHandler; ///  /// Initializes a new instance of the  class. ///  /// The remove event handler function. private WeakEvent(Action removeEventHandler) { this.removeEventHandler = removeEventHandler; } ///  /// Weakly registers the specified subscriber to the the given event of type /// . ///  ///  /// Application application; /// WeakEvent.Register{TextBox, TextChangedEventArgs>( /// this, /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); ///  /// The type of the subscriber. /// The subscriber. /// The add eventhandler. /// The remove event handler function. /// The event execution function. public static WeakEvent Register( S subscriber, Action addEventhandler, Action removeEventHandler, Action action) where S : class { return Register( subscriber, eventHandler => (sender, e) => eventHandler(sender, e), addEventhandler, removeEventHandler, action); } ///  /// Weakly registers the specified subscriber to the the given event of type /// . ///  ///  /// Application application; /// WeakEvent.Register{TextBox, TextChangedEventArgs>( /// this, /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); ///  /// The type of the subscriber. /// The type of the event arguments. /// The subscriber. /// The add eventhandler. /// The remove event handler function. /// The event execution function. public static WeakEvent Register( S subscriber, Action> addEventhandler, Action> removeEventHandler, Action action) where S : class where TEventArgs : EventArgs { return Register, TEventArgs>( subscriber, eventHandler => eventHandler, addEventhandler, removeEventHandler, action); } ///  /// Weakly registers the specified subscriber to the the given event. ///  ///  /// TextBox textbox; /// WeakEvent.Register{TextBox, TextChangedEventHandler, TextChangedEventArgs>( /// this, /// eventHandler => (sender, e) => eventHandler(sender, e), /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); ///  /// The type of the subscriber. /// The type of the event handler. /// The type of the event arguments. /// The subscriber. /// The get event handler function. /// The add event handler function. /// The remove event handler function. /// The event execution function. public static WeakEvent Register( S subscriber, Func, TEventHandler> getEventHandler, Action addEventHandler, Action removeEventHandler, Action action) where S : class where TEventHandler : class where TEventArgs : EventArgs { WeakReference weakReference = new WeakReference(subscriber); TEventHandler eventHandler = null; eventHandler = getEventHandler( new EventHandler( (sender, e) => { S subscriberStrongRef = weakReference.Target as S; if (subscriberStrongRef != null) { action(subscriberStrongRef, e); } else { removeEventHandler(eventHandler); eventHandler = null; } })); addEventHandler(eventHandler); return new WeakEvent(() => removeEventHandler(eventHandler)); } public static WeakEvent Register( S subscriber, Action addEventHandler, Action removeEventHandler, Action action) where S : class { WeakReference weakReference = new WeakReference(subscriber); DependencyPropertyChangedEventHandler eventHandler = null; eventHandler = new DependencyPropertyChangedEventHandler( (sender, e) => { S subscriberStrongRef = weakReference.Target as S; if (subscriberStrongRef != null) { action(subscriberStrongRef, e); } else { removeEventHandler(eventHandler); eventHandler = null; } }); addEventHandler(eventHandler); return new WeakEvent(() => removeEventHandler(eventHandler)); } ///  /// Manually unregisters this instance from the event. ///  public void Unregister() { if (this.removeEventHandler != null) { this.removeEventHandler(); this.removeEventHandler = null; } } } 

El enfoque de Dustin Campbell ya es excelente. Lo único que queda, guardar una solución integrada en .NET, es una forma realmente simple de crear manejadores de eventos débiles realmente generics:

http://puremsil.wordpress.com/2010/05/03/generic-weak-event-handlers/