the example event custom c# event-handling garbage-collection

c# - example - Recolección de basura cuando se usan delegados anónimos para el manejo de eventos



publish event c# (4)

Algunos ejemplos de código que hice recientemente, basados ​​en WeakReference:

// strongly typed weak reference public class WeakReference<T> : WeakReference where T : class { public WeakReference(T target) : base(target) { } public WeakReference(T target, bool trackResurrection) : base(target, trackResurrection) { } public new T Target { get { return base.Target as T; } set { base.Target = value; } } } // weak referenced generic event handler public class WeakEventHandler<TEventArgs> : WeakReference<EventHandler<TEventArgs>> where TEventArgs : EventArgs { public WeakEventHandler(EventHandler<TEventArgs> target) : base(target) { } protected void Invoke(object sender, TEventArgs e) { if (Target != null) { Target(sender, e); } } public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> weakEventHandler) { if (weakEventHandler != null) { if (weakEventHandler.IsAlive) { return weakEventHandler.Invoke; } } return null; } } // weak reference common event handler public class WeakEventHandler : WeakReference<EventHandler> { public WeakEventHandler(EventHandler target) : base(target) { } protected void Invoke(object sender, EventArgs e) { if (Target != null) { Target(sender, e); } } public static implicit operator EventHandler(WeakEventHandler weakEventHandler) { if (weakEventHandler != null) { if (weakEventHandler.IsAlive) { return weakEventHandler.Invoke; } } return null; } } // observable class, fires events public class Observable { public Observable() { Console.WriteLine("new Observable()"); } ~Observable() { Console.WriteLine("~Observable()"); } public event EventHandler OnChange; protected virtual void DoOnChange() { EventHandler handler = OnChange; if (handler != null) { Console.WriteLine("DoOnChange()"); handler(this, EventArgs.Empty); } } public void Change() { DoOnChange(); } } // observer, event listener public class Observer { public Observer() { Console.WriteLine("new Observer()"); } ~Observer() { Console.WriteLine("~Observer()"); } public void OnChange(object sender, EventArgs e) { Console.WriteLine("-> Observer.OnChange({0}, {1})", sender, e); } } // sample usage and test code public static class Program { static void Main() { Observable subject = new Observable(); Observer watcher = new Observer(); Console.WriteLine("subscribe new WeakEventHandler()/n"); subject.OnChange += new WeakEventHandler(watcher.OnChange); subject.Change(); Console.WriteLine("/nObserver = null, GC"); watcher = null; GC.Collect(0, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); subject.Change(); if (Debugger.IsAttached) { Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } } }

Genera el siguiente resultado:

new Observable() new Observer() subscribe new WeakEventHandler() DoOnChange() -> Observer.OnChange(ConsoleApplication4.Observable, System.EventArgs) Observer = null, GC ~Observer() DoOnChange() ~Observable() Press any key to continue . . .

(Tenga en cuenta que darse de baja (- =) no funciona)

ACTUALIZAR

He combinado varias respuestas desde aquí en una respuesta "definitiva" sobre una nueva pregunta .

Pregunta original

En mi código tengo un editor de eventos, que existe durante toda la vida útil de la aplicación (aquí se reduce a lo esencial):

public class Publisher { //ValueEventArgs<T> inherits from EventArgs public event EventHandler<ValueEventArgs<bool>> EnabledChanged; }

Debido a que este editor se puede usar en cualquier lugar, me sentí bastante satisfecho de crear esta pequeña clase de ayudante para evitar volver a escribir el código de manejo en todos los suscriptores:

public static class Linker { public static void Link(Publisher publisher, Control subscriber) { publisher.EnabledChanged += (s, e) => subscriber.Enabled = e.Value; } //(Non-lambda version, if you''re not comfortable with lambdas) public static void Link(Publisher publisher, Control subscriber) { publisher.EnabledChanged += delegate(object sender, ValueEventArgs<bool> e) { subscriber.Enabled = e.Value; }; } }

Funcionó bien, hasta que comenzamos a usarlo en máquinas más pequeñas, cuando comencé a obtener lo ocasional:

System.ComponentModel.Win32Exception Not enough storage is available to process this command

Resulta que hay un lugar en el código donde los controles de los suscriptores se crean, agregan y eliminan dinámicamente de un formulario. Dado mi conocimiento avanzado de la recolección de basura, etc. (es decir, ninguno, hasta ayer), nunca pensé en aclarar detrás de mí, ya que en la gran mayoría de los casos, los suscriptores también viven durante la vida de la aplicación.

He jugueteado un rato con WeakEventHandler de Dustin Campbell , pero no funciona con delegados anónimos (no para mí de todos modos).

¿Hay alguna forma de este problema? Realmente me gustaría evitar tener que copiar y pegar el código de la placa de la caldera en toda la tienda.

(Ah, y no te molestes en preguntarme POR QUÉ estamos creando y destruyendo controles todo el tiempo, no fue mi decisión de diseño ...)

(PD: es una aplicación de winforms, pero nos hemos actualizado a VS2008 y .Net 3.5, ¿debería considerar usar el patrón de Evento Débil ?)

(PPS: Buena respuesta de Rory , pero si alguien puede encontrar un equivalente al WeakEventHandler que me evite tener que acordarme de explícitamente UnLink / Dispose, eso sería genial ...)

EDITAR Debo admitir que trabajé alrededor de este problema "reciclando" los controles en cuestión. Sin embargo, la solución ha vuelto a atormentarme ya que la ''clave'' que estaba usando aparentemente no es única (sollozo). Acabo de descubrir otros enlaces aquí (intenté esto - parece ser demasiado débil - GC borra delegados incluso si el objetivo todavía está vivo, el mismo problema con s, oɔɯǝɹ respuesta abajo), aquí (obliga a modificar el editor, y doesn realmente funciona con delegados anónimos) y aquí (citado como incompleto por Dustin Campbell).

Se me ocurre que lo que estoy buscando puede ser semánticamente imposible: los cierres están diseñados para ''aguantar incluso después de que me haya ido''.

He encontrado otra solución, así que me quedaré con eso, a la espera de una voz de los dioses .


Sé que esta pregunta es antigua, pero el infierno, la encontré, y creo que otros también podrían. Estoy tratando de resolver un problema relacionado, y podría tener alguna idea.

Usted mencionó WeakEventHandler de Dustin Campbell; de hecho, no puede funcionar con métodos anónimos por diseño. Estaba tratando de tocar algo juntos que, cuando me di cuenta de que a) en el 99% de los casos necesitaría algo así, su solución original sería más segura, yb) en los pocos casos en que tengo que hacerlo (nota: tener para, no "querer porque lambdas es mucho más bonito y conciso") es posible hacerlo funcionar si te vuelves un poco listo.

Su ejemplo parece exactamente el tipo de caso aislado en el que hacerse un poco complicado puede dar como resultado una solución bastante concisa.

public static class Linker { public static void Link(Publisher publisher, Control subscriber) { // anonymous method references the subscriber only through weak // references,so its existance doesn''t interfere with garbage collection var subscriber_weak_ref = new WeakReference(subscriber); // this instance variable will stay in memory as long as the anonymous // method holds a reference to it we declare and initialize it to // reserve the memory (also, compiler complains about uninitialized // variable otherwise) EventHandler<ValueEventArgs<bool>> handler = null; // when the handler is created it will grab references to the local // variables used within, keeping them in memory after the function // scope ends 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 { // unsubscribing the delegate from within itself is risky, but // because only one instance exists and nobody else has a // reference to it we can do this ((Publisher)sender).EnabledChanged -= handler; // by assigning the original instance variable pointer to null // we make sure that nothing else references the anonymous // method and it can be collected. After this, the weak // reference and the handler pointer itselfwill be eligible for // collection as well. handler = null; } }; publisher.EnabledChanged += handler; } }

Se rumorea que el patrón WPF Weak Event viene con muchos gastos generales, por lo que en esta situación particular no lo usaría. Además, hacer referencia a la biblioteca principal WPF en una aplicación WinForm parece un poco pesado también.


Si conserva una referencia al delegado anónimo y luego lo elimina cuando los controles se eliminan del formulario que debería permitir que tanto los controles como los delegados anónimos sean recolectados.

Entonces algo como esto:

public static class Linker { //(Non-lambda version, I''m not comfortable with lambdas:) public static EventHandler<ValueEventArgs<bool>> Link(Publisher publisher, Control subscriber) { EventHandler<ValueEventArgs<bool>> handler = delegate(object sender, ValueEventArgs<bool> e) { subscriber.Enabled = e.Value; }; publisher.EnabledChanged += handler; return handler; } public static void UnLink(Publisher publisher, EventHandler<ValueEventArgs<bool>> handler) { publisher.EnabledChanged -= handler; } }

Consulte Anular suscripción método anónimo en C # para ver un ejemplo de eliminación de delegados.


Continuando con la respuesta de Egor , quería probar y construir una versión en la que no tuviera que determinar de antemano a qué evento deseaba adjuntarme.

Solo he logrado hacerlo funcionar con manejadores de eventos genéricos: para manejadores de eventos ''estándar'' (por ejemplo, FormClosingEventHandler), es un poco complicado, porque no puedes tener una restricción de tipo where T : delegate (a menos que tu nombre termine con Pony ).

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 consume) Action<S, T> consume) //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"); consume(subscriber_strong_ref, e); } else { remove(handler); handler = null; } }; add(handler); }

(* EventHandler<T> consume aquí, pero el código de llamada se pone feo porque tienes que enviar s al Suscriptor en el consumo lambda).

Ejemplo de código de llamada, tomado del ejemplo anterior:

SetAnyGenericHandler( h => publisher.EnabledChanged += h, h => publisher.EnabledChanged -= h, subscriber, (Subscriber s, ValueEventArgs<bool> e) => s.Enabled = e.Value);

O, si lo prefiere

SetAnyGenericHandler<Subscriber, ValueEventArgs<bool>>( h => publisher.EnabledChanged += h, h => publisher.EnabledChanged -= h, subscriber, (s, e) => s.Enabled = e.Value);

Sería bueno poder pasar el Evento como solo un parámetro, pero no se puede acceder a agregar / eliminar de un evento más de lo que se puede acceder a get / set desde una propiedad (sin hacer cosas de reflexión desagradables, creo )