Słabe wydarzenia in.NET?

Jeśli obiekt a nasłuchuje zdarzenia z obiektu B, obiekt B utrzyma obiekt a przy życiu. Czy istnieje standardowa implementacja słabych zdarzeń, które mogłyby temu zapobiec? Wiem, że WPF ma jakiś mechanizm, ale szukam czegoś nie związanego z WPF. Domyślam się, że rozwiązanie powinno używać słabych odniesień.

Author: tom7, 2009-07-07

9 answers

Dustin Campbell z DidItWith.NET blog analizuje kilka nieudanych prób stworzenia słabych programów obsługi zdarzeń, a następnie pokazuje poprawną, działającą, lekką implementację: rozwiązującą Problem ze słabymi programami obsługi zdarzeń .

W idealnej sytuacji Microsoft wprowadziłby tę koncepcję do samego języka. Coś w stylu:
Foo.Clicked += new weak EventHandler(...);

Jeśli uważasz, że ta funkcja jest dla ciebie ważna, zagłosuj na nią tutaj .

 44
Author: Judah Himango,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-17 09:42:36

Przepakowałem implementację Dustina Campbella, aby ułatwić jej rozszerzenie na różne typy zdarzeń, gdy nie są używane ogólne programy obsługi. Myślę, że może się komuś przydać.

Napisy:
oryginalna realizacja Pana Campbella
Bardzo poręczna funkcja rzucania delegata przez Eda Balla, link można znaleźć w source

Obsługa i kilka przeciążeń, nawet Thander i 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;
  }
 }
}

Testy jednostkowe:


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
 }
}
 10
Author: Egor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-27 07:22:57

Użycie zalecanego wzorca Dispose () , Gdzie zdarzenia są zarządzanym zasobem do oczyszczenia, powinno się tym zająć. Obiekt A powinien wyrejestrować się jako słuchacz zdarzeń z obiektu B, gdy zostanie usunięty...

 1
Author: Arnshea C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-06 21:38:13

Podejście Dustina Campbella jest już doskonałe. Jedyne, co pozostało, zapisanie rozwiązania zintegrowanego z. NET, to naprawdę prosty sposób na stworzenie naprawdę ogólnych, słabych procedur obsługi zdarzeń:

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

 0
Author: Stefan Dragnev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-06 20:47:14

Implementacja Dustina działa tylko z delegatami EventHandler. Jeśli udasz się do CodePlex jest projekt o nazwie Sharp Observation , w którym autor zbudował bardzo dobry słaby dostawca delegatów. Jest on zaimplementowany w MSIL i jest znacznie szybszy i bardziej elastyczny.

... co, dopóki Microsoft nie zaimplementuje słabych zdarzeń natywnie, będzie musiał zrobić.

 0
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-28 19:49:51

Istnieje również rozwiązanie, które działa w Silverlight / WP7, które używa wyrażeń Linq zamiast MSIL emit.

Http://socialeboladev.wordpress.com/2012/09/23/weak-event-implementation-that-works-for-any-event-type/

 0
Author: Shahar Prish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-24 05:17:11

Ważny szczegół:

Implementacja Dustina eliminuje silne odniesienie wprowadzone przez obsługę zdarzeń, ale może wprowadzić nowy wyciek pamięci (przynajmniej jeśli nie zwracamy na to wystarczającej uwagi).

Ponieważ wywołanie zwrotne niezarejestrowane nie jest traktowane jako słaba obsługa zdarzeń, może zawierać silne odniesienie do jakiegoś obiektu. Zależy to od tego, czy zadeklarujesz niezarejestrowane wywołanie zwrotne w klasie Abonenta zdarzenia, czy nie.

Jeśli tego nie zrobisz, wywołanie zwrotne zostanie powiązane z odniesieniem do zamykającej się instancji klasy. Oto przykład tego, do czego się odwołuję: po zadeklarowaniu niezarejestrowanego wywołania zwrotnego będzie ono zawierało odniesienie do instancji klasy Programu:

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);
    }
}
 0
Author: Dennis Kassel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-11 01:50:37

Jakie zalety ma implementacja Dustina w porównaniu z klasą WEAKEVENTMANAGER WPF, która po prostu zawija obiekt docelowy, jak również delegata w słabe odniesienie:

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

Moim zdaniem takie podejście jest bardziej elastyczne, ponieważ nie wymaga, aby implementacja przekazywała instancję docelową jako parametr podczas wywoływania obsługi zdarzenia:

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

            if (target != null)
                m_OpenHandler(target, sender, e);

Pozwala to również na użycie metod anomymous zamiast metody instancji (która wydaje się być również wadą implementacji).

 0
Author: Dennis Kassel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-11 15:36:41

Należy zachować ostrożność podczas korzystania z implementacji słabych zdarzeń. Wydaje się, że słabe zdarzenia usuwają z Abonenta odpowiedzialność za anulowanie subskrypcji wydarzenia (lub wiadomości). W takim przypadku procedury obsługi zdarzeń mogą być wywoływane nawet po tym, jak abonent "wyjdzie poza zakres". Weź subskrybenta, który nie wyraźnie wypisuje się z subskrypcji i który staje się śmieciowy, ale nie jest jeszcze zbierany. Menedżer słabych zdarzeń nie będzie w stanie wykryć tego stanu i z tego powodu nadal wywoła zdarzenie opiekun tego abonenta. Może to prowadzić do wszelkiego rodzaju nieoczekiwanych skutków ubocznych.

Zobacz więcej szczegółów na słaby wzorzec zdarzeń jest niebezpieczny .
Zobacz ten kod źródłowy , który ilustruje ten problem przy użyciu wtyczki MvvMCross messaging jako słabego menedżera zdarzeń.

 0
Author: Ladi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-20 03:21:37