在.NET弱的事件?(Weak events in .NET?)

2019-06-25 04:53发布

如果对象的监听从对象B的事件,对象B将继续反对为生。 有没有一个标准的实施,将防止这种微弱的事件? 我知道WPF有一定的机制,但我期待的东西不依赖于WPF。 我猜的解决方案应该某处使用弱引用。

Answer 1:

从DidItWith.NET博客达斯汀·坎贝尔检查几个失败的尝试创建弱的事件处理程序,然后继续表现出有效的工作,轻量级实现: 解决问题的弱事件处理程序 。

理想的情况是,微软将推出概念引入到语言本身。 就像是:

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

如果你觉得这个功能对你很重要,请在这里为它投票 。



Answer 2:

我重新包装达斯汀·坎贝尔的实现,使其更容易一点,当不使用通用处理器的不同事件类型扩展。 我想,这可能是一些使用的人的。

积分:
坎贝尔先生的最初实现
一个非常方便的委托由Ed球投功能,链接可以在源中找到有关

处理程序和一对夫妇重载,EventHander <E>和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;
  }
 }
}

单元测试:


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
 }
}


Answer 3:

使用建议的Dispose()模式 ,在那里你考虑事件管理的资源进行清理,应对此进行处理。 对象时,它的配置在应注销本身从对象B事件监听器...



Answer 4:

达斯汀·坎贝尔的做法已经非常出色。 唯一剩下的东西,节省集成到.NET的解决方案,是一个非常简单的方法来创建真正通用的弱事件处理程序:

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



Answer 5:

达斯汀的执行只适用于事件处理程序的代表。 如果你头部到CodePlex上,有一个名为项目敏锐的观察中,笔者已经建立了一个很好的薄弱委托供应商。 它是在MSIL实现,是相当更快,更灵活。

......这,直到微软实现弱事件本身,将不得不这样做。



Answer 6:

还有的是在Silverlight / WP7它使用LINQ表达式,而不是MSIL发出有效的解决方案。

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



Answer 7:

一个重要的细节:

达斯汀的实施消除了事件处理程序出台了有力的参考,但它可能会引入新的内存泄漏(至少没有给予足够的重视时)。

由于注销回调而不是作为一个软弱的事件处理程序处理可能含有的强引用某个对象。 这取决于你是否声明在事件订阅类或不注销回调。

如果不这样做,回调将与封装类实例的引用关联。 下面是什么我指的例子:声明注销回调后,它会包含对程序类实例的引用:

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);
    }
}


Answer 8:

哪些优点达斯汀的实现已经比WPF的WeakEventManager的类,它只是包装了目标对象以及委托到一个弱引用:

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

在我看来,这种方法更为灵活,因为它不需要执行到目标实例传递的事件处理程序的调用过程中的参数:

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

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

这也允许使用anomymous方法而不是一个实例方法(这似乎是也是达斯汀的实现的缺点)。



Answer 9:

使用弱事件的实现时要小心。 弱事件显示,从用户删除的事件(或消息)退订的责任。 在这种情况下,事件处理程序可以调用的用户“超出范围”即使经过。 以用户不明确退订,就变成了垃圾收集的,但还没有垃圾收集。 弱事件管理器将无法检测到该州正因为如此它仍然会调用用户的事件处理程序。 这会导致所有样的意想不到的副作用。

查看更多详细信息,在弱事件模式是危险的 。
看到这个源代码 ,说明使用MvvMCross消息插件作为一个弱事件管理器这个问题。



文章来源: Weak events in .NET?