c# .net events memory-leaks lambda

c# - Modelo de controlador de eventos débil para uso con lambdas



.net events (4)

''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<ValueEventArgs> ValueEvent :

// for ''vanilla'' events SetAnyHandler<Subscriber, MouseEventHandler, MouseEventArgs>( 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<Subscriber, ValueEventArgs>( 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<S, TDelegate, TArgs>( Func<EventHandler<TArgs>, TDelegate> converter, Action<TDelegate> add, Action<TDelegate> remove, S subscriber, Action<S, TArgs> action) where TArgs : EventArgs where TDelegate : class where S : class { var subs_weak_ref = new WeakReference(subscriber); TDelegate handler = null; handler = converter(new EventHandler<TArgs>( (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<S, TArgs>( Action<EventHandler<TArgs>> add, Action<EventHandler<TArgs>> remove, S subscriber, Action<S, TArgs> action) where TArgs : EventArgs where S : class { SetAnyHandler<S, EventHandler<TArgs>, 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<ValueEventArgs<bool>> handler = null; handler = delegate(object sender, ValueEventArgs<bool> 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<S, T>( Action<EventHandler<T>> add, //to add event listener to publisher Action<EventHandler<T>> remove, //to remove event listener from publisher S subscriber, //ref to subscriber (to pass to action) Action<S, T> action) //called when event is raised where T : EventArgs where S : class { var subscriber_weak_ref = new WeakReference(subscriber); EventHandler<T> 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<T, R>( Action<T> add, //to add event listener to publisher Action<T> remove, //to remove event listener from publisher Subscriber subscriber, //ref to subscriber (to pass to action) Action<Subscriber, R> 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é here , esto no compilará, 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<MouseEventHandler, MouseDownEventArgs>( 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 .

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:

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 alcance, esta respuesta es para usted.



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.

/// <summary> /// Weakly registers for events using <see cref="WeakReference"/>. /// </summary> public sealed class WeakEvent { private Action removeEventHandler; /// <summary> /// Initializes a new instance of the <see cref="WeakEvent"/> class. /// </summary> /// <param name="removeEventHandler">The remove event handler function.</param> private WeakEvent(Action removeEventHandler) { this.removeEventHandler = removeEventHandler; } /// <summary> /// Weakly registers the specified subscriber to the the given event of type /// <see cref="EventHandler"/>. /// </summary> /// <example> /// Application application; /// WeakEvent.Register{TextBox, TextChangedEventArgs>( /// this, /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); /// </example> /// <typeparam name="S">The type of the subscriber.</typeparam> /// <param name="subscriber">The subscriber.</param> /// <param name="addEventhandler">The add eventhandler.</param> /// <param name="removeEventHandler">The remove event handler function.</param> /// <param name="action">The event execution function.</param> public static WeakEvent Register<S>( S subscriber, Action<EventHandler> addEventhandler, Action<EventHandler> removeEventHandler, Action<S, EventArgs> action) where S : class { return Register<S, EventHandler, EventArgs>( subscriber, eventHandler => (sender, e) => eventHandler(sender, e), addEventhandler, removeEventHandler, action); } /// <summary> /// Weakly registers the specified subscriber to the the given event of type /// <see cref="EventHandler{T}"/>. /// </summary> /// <example> /// Application application; /// WeakEvent.Register{TextBox, TextChangedEventArgs>( /// this, /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); /// </example> /// <typeparam name="S">The type of the subscriber.</typeparam> /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam> /// <param name="subscriber">The subscriber.</param> /// <param name="addEventhandler">The add eventhandler.</param> /// <param name="removeEventHandler">The remove event handler function.</param> /// <param name="action">The event execution function.</param> public static WeakEvent Register<S, TEventArgs>( S subscriber, Action<EventHandler<TEventArgs>> addEventhandler, Action<EventHandler<TEventArgs>> removeEventHandler, Action<S, TEventArgs> action) where S : class where TEventArgs : EventArgs { return Register<S, EventHandler<TEventArgs>, TEventArgs>( subscriber, eventHandler => eventHandler, addEventhandler, removeEventHandler, action); } /// <summary> /// Weakly registers the specified subscriber to the the given event. /// </summary> /// <example> /// 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)); /// </example> /// <typeparam name="S">The type of the subscriber.</typeparam> /// <typeparam name="TEventHandler">The type of the event handler.</typeparam> /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam> /// <param name="subscriber">The subscriber.</param> /// <param name="getEventHandler">The get event handler function.</param> /// <param name="addEventHandler">The add event handler function.</param> /// <param name="removeEventHandler">The remove event handler function.</param> /// <param name="action">The event execution function.</param> public static WeakEvent Register<S, TEventHandler, TEventArgs>( S subscriber, Func<EventHandler<TEventArgs>, TEventHandler> getEventHandler, Action<TEventHandler> addEventHandler, Action<TEventHandler> removeEventHandler, Action<S, TEventArgs> action) where S : class where TEventHandler : class where TEventArgs : EventArgs { WeakReference weakReference = new WeakReference(subscriber); TEventHandler eventHandler = null; eventHandler = getEventHandler( new EventHandler<TEventArgs>( (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>( S subscriber, Action<DependencyPropertyChangedEventHandler> addEventHandler, Action<DependencyPropertyChangedEventHandler> removeEventHandler, Action<S, DependencyPropertyChangedEventArgs> 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)); } /// <summary> /// Manually unregisters this instance from the event. /// </summary> public void Unregister() { if (this.removeEventHandler != null) { this.removeEventHandler(); this.removeEventHandler = null; } } }


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

Action<int,int> myDelegate = new Action<int,int>( aMethodOnMyClass ); myDelegate.MakeWeak();

¡Tan fácil como eso!