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:
- ¿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 alcance, esta respuesta es para usted.
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 genéricos:
http://puremsil.wordpress.com/2010/05/03/generic-weak-event-handlers/
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!