.net events garbage-collection weak-references

¿Eventos débiles en.NET?



events garbage-collection (9)

Si el objeto A escucha un evento del objeto B, el objeto B mantendrá vivo el objeto A. ¿Hay una implementación estándar de eventos débiles que evitaría esto? Sé que WPF tiene algún mecanismo, pero estoy buscando algo que no esté relacionado con WPF. Supongo que la solución debería usar referencias débiles en alguna parte.


¿Qué ventajas tiene la implementación de Dustin en comparación con la clase WeakEventManager de WPF que simplemente envuelve el objeto de destino y el delegado en una referencia débil?

public Listener(object target, Delegate handler) { this._target = new WeakReference(target); this._handler = new WeakReference((object) handler); }

En mi opinión, este enfoque es más flexible, ya que no requiere que la implementación pase la instancia de destino como un parámetro durante la invocación del controlador de eventos:

public void Invoke(object sender, E e) { T target = (T)m_TargetRef.Target; if (target != null) m_OpenHandler(target, sender, e);

Esto también permite el uso de métodos anómalos en lugar de un método de instancia (que también parece ser una desventaja de la implementación de Dustin).


Dustin Campbell, del blog DidItWith.NET, examina varios de los intentos fallidos para crear controladores de eventos débiles, y luego muestra una implementación ligera, funcional y válida: Resolviendo el problema con manejadores de eventos débiles .

Idealmente, sin embargo, Microsoft introduciría el concepto en el lenguaje mismo. Algo como:

Foo.Clicked += new weak EventHandler(...);

Si crees que esta característica es importante para ti, vota aquí .



La implementación de Dustin solo funciona con los delegados de EventHandler. Si te diriges a CodePlex, hay un proyecto llamado sharpobservation.codeplex.com en el que el autor ha creado un muy buen proveedor de delegados débiles. Está implementado en MSIL y es considerablemente más rápido y flexible.

... que, hasta que Microsoft implemente los eventos débiles de forma nativa, tendrá que hacer.



Tenga cuidado al usar implementaciones de eventos débiles. Los eventos débiles parecen eliminar del suscriptor la responsabilidad de darse de baja del evento (o mensaje). En ese caso, los manejadores de eventos pueden invocarse incluso después de que el suscriptor "salga del alcance". Tome un suscriptor que no anule la suscripción explícitamente y que se convierta en coleccionable de basura pero que aún no se haya recogido basura. El administrador de eventos débiles no podrá detectar ese estado y, debido a eso, seguirá llamando al controlador de eventos de ese suscriptor. Esto puede conducir a todo tipo de efectos secundarios inesperados.

Ver más detalles en The Weak Event Pattern is Dangerous .
Consulte este código fuente que ilustra este problema utilizando el complemento de mensajería de MvvMCross como administrador de eventos débiles.


Un detalle importante:

La implementación de Dustin elimina una referencia fuerte introducida por el manejador de eventos, pero puede presentar una nueva pérdida de memoria (al menos cuando no se presta suficiente atención).

Como la devolución de llamada sin registrar no se trata como un controlador de eventos débiles, puede contener una referencia fuerte a algún objeto. Depende de si declara o no la devolución de llamada eliminada del registro en la clase de suscriptor del evento.

Si no lo hace, la devolución de llamada se asociará con una referencia a la instancia de clase adjunta. Aquí hay un ejemplo de lo que estoy refiriendo: después de declarar la devolución de llamada anular el registro contendrá una referencia a la instancia de clase de programa:

public class EventSource { public event EventHandler<EventArgs> Fired } } public class EventSubscriber { public void OnEventFired(object sender, EventArgs) { ; } } public class Program { public void Main() { var source = new EventSource(); var subscriber = new EventSubscriber(); source.Fired += new WeakEventHandler<EventSubscriber, EventArgs>(subscriber.OnEventFired, handler => source.Fired -= handler); } }


Utilizando el patrón Dispose () recomendado , donde se consideran los eventos que un recurso administrado para limpiar, debe manejar esto. El objeto A debe anular el registro como un oyente de eventos del objeto B cuando se elimina ...


Volví a empaquetar la implementación de Dustin Campbell para facilitar un poco la ampliación para diferentes tipos de eventos cuando no se usan manipuladores genéricos. Me imagino que puede ser de alguna utilidad para alguien.

Créditos:
La implementación original del Sr. Campbell
Una función de elenco delegate muy útil por Ed Ball, enlace se puede encontrar en la fuente

El controlador y un par de sobrecargas, EventHander <E> y PropertyChangedEventHandler:

/// Basic weak event management. /// /// Weak allow objects to be garbage collected without having to unsubscribe /// /// Taken with some minor variations from: /// http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx /// /// use as class.theEvent +=new EventHandler<EventArgs>(instance_handler).MakeWeak((e) => class.theEvent -= e); /// MakeWeak extension methods take an delegate to unsubscribe the handler from the event /// using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; namespace utils { /// <summary> /// Delegate of an unsubscribe delegate /// </summary> public delegate void UnregisterDelegate<H>(H eventHandler) where H : class; /// <summary> /// A handler for an event that doesn''t store a reference to the source /// handler must be a instance method /// </summary> /// <typeparam name="T">type of calling object</typeparam> /// <typeparam name="E">type of event args</typeparam> /// <typeparam name="H">type of event handler</typeparam> public class WeakEventHandlerGeneric<T, E, H> where T : class where E : EventArgs where H : class { private delegate void OpenEventHandler(T @this, object sender, E e); private delegate void LocalHandler(object sender, E e); private WeakReference m_TargetRef; private OpenEventHandler m_OpenHandler; private H m_Handler; private UnregisterDelegate<H> m_Unregister; public WeakEventHandlerGeneric(H eventHandler, UnregisterDelegate<H> unregister) { m_TargetRef = new WeakReference((eventHandler as Delegate).Target); m_OpenHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler), null, (eventHandler as Delegate).Method); m_Handler = CastDelegate(new LocalHandler(Invoke)); m_Unregister = unregister; } private void Invoke(object sender, E e) { T target = (T)m_TargetRef.Target; if (target != null) m_OpenHandler.Invoke(target, sender, e); else if (m_Unregister != null) { m_Unregister(m_Handler); m_Unregister = null; } } /// <summary> /// Gets the handler. /// </summary> public H Handler { get { return m_Handler; } } /// <summary> /// Performs an implicit conversion from <see cref="PR.utils.WeakEventHandler&lt;T,E&gt;"/> to <see cref="System.EventHandler&lt;E&gt;"/>. /// </summary> /// <param name="weh">The weh.</param> /// <returns>The result of the conversion.</returns> public static implicit operator H(WeakEventHandlerGeneric<T, E, H> weh) { return weh.Handler; } /// <summary> /// Casts the delegate. /// Taken from /// http://jacobcarpenters.blogspot.com/2006/06/cast-delegate.html /// </summary> /// <param name="source">The source.</param> /// <returns></returns> public static H CastDelegate(Delegate source) { if (source == null) return null; Delegate[] delegates = source.GetInvocationList(); if (delegates.Length == 1) return Delegate.CreateDelegate(typeof(H), delegates[0].Target, delegates[0].Method) as H; for (int i = 0; i < delegates.Length; i++) delegates[i] = Delegate.CreateDelegate(typeof(H), delegates[i].Target, delegates[i].Method); return Delegate.Combine(delegates) as H; } } #region Weak Generic EventHandler<Args> handler /// <summary> /// An interface for a weak event handler /// </summary> /// <typeparam name="E"></typeparam> public interface IWeakEventHandler<E> where E : EventArgs { EventHandler<E> Handler { get; } } /// <summary> /// A handler for an event that doesn''t store a reference to the source /// handler must be a instance method /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="E"></typeparam> public class WeakEventHandler<T, E> : WeakEventHandlerGeneric<T, E, EventHandler<E>>, IWeakEventHandler<E> where T : class where E : EventArgs { public WeakEventHandler(EventHandler<E> eventHandler, UnregisterDelegate<EventHandler<E>> unregister) : base(eventHandler, unregister) { } } #endregion #region Weak PropertyChangedEvent handler /// <summary> /// An interface for a weak event handler /// </summary> /// <typeparam name="E"></typeparam> public interface IWeakPropertyChangedEventHandler { PropertyChangedEventHandler Handler { get; } } /// <summary> /// A handler for an event that doesn''t store a reference to the source /// handler must be a instance method /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="E"></typeparam> public class WeakPropertyChangeHandler<T> : WeakEventHandlerGeneric<T, PropertyChangedEventArgs, PropertyChangedEventHandler>, IWeakPropertyChangedEventHandler where T : class { public WeakPropertyChangeHandler(PropertyChangedEventHandler eventHandler, UnregisterDelegate<PropertyChangedEventHandler> unregister) : base(eventHandler, unregister) {} } #endregion /// <summary> /// Utilities for the weak event method /// </summary> public static class WeakEventExtensions { private static void CheckArgs(Delegate eventHandler, Delegate unregister) { if (eventHandler == null) throw new ArgumentNullException("eventHandler"); if (eventHandler.Method.IsStatic || eventHandler.Target == null) throw new ArgumentException("Only instance methods are supported.", "eventHandler"); } private static object GetWeakHandler(Type generalType, Type[] genericTypes, Type[] constructorArgTypes, object[] constructorArgs) { var wehType = generalType.MakeGenericType(genericTypes); var wehConstructor = wehType.GetConstructor(constructorArgTypes); return wehConstructor.Invoke(constructorArgs); } /// <summary> /// Makes a property change handler weak /// </summary> /// <typeparam name="E"></typeparam> /// <param name="eventHandler">The event handler.</param> /// <param name="unregister">The unregister.</param> /// <returns></returns> public static PropertyChangedEventHandler MakeWeak(this PropertyChangedEventHandler eventHandler, UnregisterDelegate<PropertyChangedEventHandler> unregister) { CheckArgs(eventHandler, unregister); var generalType = typeof (WeakPropertyChangeHandler<>); var genericTypes = new[] {eventHandler.Method.DeclaringType}; var constructorTypes = new[] { typeof(PropertyChangedEventHandler), typeof(UnregisterDelegate<PropertyChangedEventHandler>) }; var constructorArgs = new object[] {eventHandler, unregister}; return ((IWeakPropertyChangedEventHandler) GetWeakHandler(generalType, genericTypes, constructorTypes, constructorArgs)).Handler; } /// <summary> /// Makes a generic handler weak /// </summary> /// <typeparam name="E"></typeparam> /// <param name="eventHandler">The event handler.</param> /// <param name="unregister">The unregister.</param> /// <returns></returns> public static EventHandler<E> MakeWeak<E>(this EventHandler<E> eventHandler, UnregisterDelegate<EventHandler<E>> unregister) where E : EventArgs { CheckArgs(eventHandler, unregister); var generalType = typeof(WeakEventHandler<,>); var genericTypes = new[] { eventHandler.Method.DeclaringType, typeof(E) }; var constructorTypes = new[] { typeof(EventHandler<E>), typeof(UnregisterDelegate<EventHandler<E>>) }; var constructorArgs = new object[] { eventHandler, unregister }; return ((IWeakEventHandler<E>)GetWeakHandler(generalType, genericTypes, constructorTypes, constructorArgs)).Handler; } } }

Pruebas unitarias:

using System.ComponentModel; using NUnit.Framework; using System.Collections.Generic; using System; namespace utils.Tests { [TestFixture] public class WeakEventTests { #region setup/teardown [TestFixtureSetUp] public void SetUp() { testScenarios.Add(SetupTestGeneric); testScenarios.Add(SetupTestPropChange); } [TestFixtureTearDown] public void TearDown() { } #endregion #region tests private List<Action<bool>> testScenarios = new List<Action<bool>>(); private IEventSource source; private WeakReference sourceRef; private IEventConsumer consumer; private WeakReference consumerRef; private IEventConsumer consumer2; private WeakReference consumerRef2; [Test] public void ConsumerSourceTest() { foreach(var a in testScenarios) { a(false); ConsumerSourceTestMethod(); } } private void ConsumerSourceTestMethod() { Assert.IsFalse(consumer.eventSet); source.Fire(); Assert.IsTrue(consumer.eventSet); } [Test] public void ConsumerLinkTest() { foreach (var a in testScenarios) { a(false); ConsumerLinkTestMethod(); } } private void ConsumerLinkTestMethod() { consumer = null; GC.Collect(); Assert.IsFalse(consumerRef.IsAlive); Assert.IsTrue(source.InvocationCount == 1); source.Fire(); Assert.IsTrue(source.InvocationCount == 0); } [Test] public void ConsumerLinkTestDouble() { foreach (var a in testScenarios) { a(true); ConsumerLinkTestDoubleMethod(); } } private void ConsumerLinkTestDoubleMethod() { consumer = null; GC.Collect(); Assert.IsFalse(consumerRef.IsAlive); Assert.IsTrue(source.InvocationCount == 2); source.Fire(); Assert.IsTrue(source.InvocationCount == 1); consumer2 = null; GC.Collect(); Assert.IsFalse(consumerRef2.IsAlive); Assert.IsTrue(source.InvocationCount == 1); source.Fire(); Assert.IsTrue(source.InvocationCount == 0); } [Test] public void ConsumerLinkTestMultiple() { foreach (var a in testScenarios) { a(true); ConsumerLinkTestMultipleMethod(); } } private void ConsumerLinkTestMultipleMethod() { consumer = null; consumer2 = null; GC.Collect(); Assert.IsFalse(consumerRef.IsAlive); Assert.IsFalse(consumerRef2.IsAlive); Assert.IsTrue(source.InvocationCount == 2); source.Fire(); Assert.IsTrue(source.InvocationCount == 0); } [Test] public void SourceLinkTest() { foreach (var a in testScenarios) { a(false); SourceLinkTestMethod(); } } private void SourceLinkTestMethod() { source = null; GC.Collect(); Assert.IsFalse(sourceRef.IsAlive); } [Test] public void SourceLinkTestMultiple() { SetupTestGeneric(true); foreach (var a in testScenarios) { a(true); SourceLinkTestMultipleMethod(); } } private void SourceLinkTestMultipleMethod() { source = null; GC.Collect(); Assert.IsFalse(sourceRef.IsAlive); } #endregion #region test helpers public void SetupTestGeneric(bool both) { source = new EventSourceGeneric(); sourceRef = new WeakReference(source); consumer = new EventConsumerGeneric((EventSourceGeneric)source); consumerRef = new WeakReference(consumer); if (both) { consumer2 = new EventConsumerGeneric((EventSourceGeneric)source); consumerRef2 = new WeakReference(consumer2); } } public void SetupTestPropChange(bool both) { source = new EventSourcePropChange(); sourceRef = new WeakReference(source); consumer = new EventConsumerPropChange((EventSourcePropChange)source); consumerRef = new WeakReference(consumer); if (both) { consumer2 = new EventConsumerPropChange((EventSourcePropChange)source); consumerRef2 = new WeakReference(consumer2); } } public interface IEventSource { int InvocationCount { get; } void Fire(); } public class EventSourceGeneric : IEventSource { public event EventHandler<EventArgs> theEvent; public int InvocationCount { get { return (theEvent != null)? theEvent.GetInvocationList().Length : 0; } } public void Fire() { if (theEvent != null) theEvent(this, EventArgs.Empty); } } public class EventSourcePropChange : IEventSource { public event PropertyChangedEventHandler theEvent; public int InvocationCount { get { return (theEvent != null) ? theEvent.GetInvocationList().Length : 0; } } public void Fire() { if (theEvent != null) theEvent(this, new PropertyChangedEventArgs("")); } } public interface IEventConsumer { bool eventSet { get; } } public class EventConsumerGeneric : IEventConsumer { public bool eventSet { get; private set; } public EventConsumerGeneric(EventSourceGeneric sourceGeneric) { sourceGeneric.theEvent +=new EventHandler<EventArgs>(source_theEvent).MakeWeak((e) => sourceGeneric.theEvent -= e); } public void source_theEvent(object sender, EventArgs e) { eventSet = true; } } public class EventConsumerPropChange : IEventConsumer { public bool eventSet { get; private set; } public EventConsumerPropChange(EventSourcePropChange sourcePropChange) { sourcePropChange.theEvent += new PropertyChangedEventHandler(source_theEvent).MakeWeak((e) => sourcePropChange.theEvent -= e); } public void source_theEvent(object sender, PropertyChangedEventArgs e) { eventSet = true; } } #endregion } }