Weak events in .NET?

后端 未结 9 1981
没有蜡笔的小新
没有蜡笔的小新 2020-12-04 16:37

If object A listens to an event from object B, object B will keep object A alive. Is there a standard implementation of weak events that would prevent this? I know WPF has s

9条回答
  •  眼角桃花
    2020-12-04 17:22

    I repackaged Dustin Campbell's implementation to make it a little easier to extend it for different event types when generic handlers aren't used. I figure it may be of some use to someone.

    Credits:
    Mr. Campbell's original implementation
    A very handy delegate cast function by Ed Ball, link can be found in source

    The handler and a couple of overloads, EventHander and 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(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 {
    
     /// 
     /// Delegate of an unsubscribe delegate
     /// 
     public delegate void UnregisterDelegate(H eventHandler) where H : class;
    
     /// 
     /// A handler for an event that doesn't store a reference to the source
     /// handler must be a instance method
     /// 
     /// type of calling object
     /// type of event args
     /// type of event handler
     public class WeakEventHandlerGeneric
      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 m_Unregister;
    
      public WeakEventHandlerGeneric(H eventHandler, UnregisterDelegate 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;
       }
      }
    
      /// 
      /// Gets the handler.
      /// 
      public H Handler {
       get { return m_Handler; }
      }
    
      /// 
      /// Performs an implicit conversion from  to .
      /// 
      /// The weh.
      /// The result of the conversion.
      public static implicit operator H(WeakEventHandlerGeneric weh) {
       return weh.Handler;
      }
    
      /// 
      /// Casts the delegate.
      /// Taken from
      /// http://jacobcarpenters.blogspot.com/2006/06/cast-delegate.html
      /// 
      /// The source.
      /// 
      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 handler
    
     /// 
     /// An interface for a weak event handler
     /// 
     /// 
     public interface IWeakEventHandler where E : EventArgs {
      EventHandler Handler { get; }
     }
    
     /// 
     /// A handler for an event that doesn't store a reference to the source
     /// handler must be a instance method
     /// 
     /// 
     /// 
     public class WeakEventHandler : WeakEventHandlerGeneric>, IWeakEventHandler
      where T : class
      where E : EventArgs {
    
      public WeakEventHandler(EventHandler eventHandler, UnregisterDelegate> unregister) 
       : base(eventHandler, unregister) { }
     }
    
     #endregion
    
     #region Weak PropertyChangedEvent handler
    
     /// 
     /// An interface for a weak event handler
     /// 
     /// 
     public interface IWeakPropertyChangedEventHandler {
      PropertyChangedEventHandler Handler { get; }
     }
    
     /// 
     /// A handler for an event that doesn't store a reference to the source
     /// handler must be a instance method
     /// 
     /// 
     /// 
     public class WeakPropertyChangeHandler : WeakEventHandlerGeneric, IWeakPropertyChangedEventHandler
      where T : class {
    
      public WeakPropertyChangeHandler(PropertyChangedEventHandler eventHandler, UnregisterDelegate unregister) 
       : base(eventHandler, unregister) {}
     }
    
     #endregion
    
     /// 
     /// Utilities for the weak event method
     /// 
     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);
      }
    
      /// 
      /// Makes a property change handler weak
      /// 
      /// 
      /// The event handler.
      /// The unregister.
      /// 
      public static PropertyChangedEventHandler MakeWeak(this PropertyChangedEventHandler eventHandler, UnregisterDelegate unregister) {
       CheckArgs(eventHandler, unregister);
    
       var generalType = typeof (WeakPropertyChangeHandler<>);
       var genericTypes = new[] {eventHandler.Method.DeclaringType};
       var constructorTypes = new[] { typeof(PropertyChangedEventHandler), typeof(UnregisterDelegate) };
       var constructorArgs = new object[] {eventHandler, unregister};
    
       return ((IWeakPropertyChangedEventHandler) GetWeakHandler(generalType, genericTypes, constructorTypes, constructorArgs)).Handler;
      }
    
      /// 
      /// Makes a generic handler weak
      /// 
      /// 
      /// The event handler.
      /// The unregister.
      /// 
      public static EventHandler MakeWeak(this EventHandler eventHandler, UnregisterDelegate> unregister) where E : EventArgs {
       CheckArgs(eventHandler, unregister);
    
       var generalType = typeof(WeakEventHandler<,>);
       var genericTypes = new[] { eventHandler.Method.DeclaringType, typeof(E) };
       var constructorTypes = new[] { typeof(EventHandler), typeof(UnregisterDelegate>) };
       var constructorArgs = new object[] { eventHandler, unregister };
    
       return ((IWeakEventHandler)GetWeakHandler(generalType, genericTypes, constructorTypes, constructorArgs)).Handler;
      }
     }
    }
    
    

    Unit tests:

    
    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> testScenarios = new List>();
    
      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 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(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
     }
    }
    

提交回复
热议问题