28

オブジェクトAがオブジェクトBからのイベントをリッスンする場合、オブジェクトBはオブジェクトAを存続させます。これを防ぐ弱いイベントの標準的な実装はありますか?WPFには何らかのメカニズムがあることは知っていますが、WPFに関連付けられていないものを探しています。解決策はどこかで弱参照を使用する必要があると思います。

4

9 に答える 9

48

DidItWith.NETブログのDustinCampbellは、弱いイベントハンドラーを作成するために失敗したいくつかの試みを調べ、次に、有効で機能する軽量の実装を示します。弱いイベントハンドラーの問題の解決

ただし、理想的には、Microsoftはその概念を言語自体に導入します。何かのようなもの:

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

この機能が重要だと思われる場合は、こちらから投票してください

于 2009-07-06T21:45:42.523 に答える
10

Dustin Campbellの実装を再パッケージ化して、汎用ハンドラーが使用されていない場合に、さまざまなイベントタイプに拡張するのが少し簡単になるようにしました。私はそれが誰かにいくらか役立つかもしれないと思います。

クレジット:
キャンベル氏の元の実装
エドボールによる非常に便利なデリゲートキャスト関数、リンクはソースにあります

ハンドラーといくつかのオーバーロード、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
 }
}
于 2009-09-27T07:22:57.317 に答える
0

MSILエミットの代わりにLinq式を使用するSilverlight/WP7で機能するソリューションもあります。

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

于 2012-09-24T05:17:11.197 に答える
0

管理対象リソースがクリーンアップするイベントを検討する、推奨されるDispose()パターンを使用して、これを処理する必要があります。オブジェクトAは、破棄されたときに、オブジェクトBからのイベントのリスナーとしての登録を解除する必要があります。

于 2009-07-06T21:38:13.910 に答える
0

ダスティンキャンベルのアプローチはすでに優れています。残っているのは、.NETに統合されたソリューションを保存することだけです。これは、非常に一般的な弱いイベントハンドラーを作成するための非常に簡単な方法です。

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

于 2010-05-06T20:47:14.483 に答える
0

Dustinの実装は、EventHandlerデリゲートでのみ機能します。CodePlexに向かうと、 SharpObservationというプロジェクトがあります。このプロジェクトでは著者が非常に優れた弱いデリゲートプロバイダーを構築しています。これはMSILに実装されており、かなり高速で柔軟性があります。

...これは、Microsoftが弱いイベントをネイティブに実装するまで、実行する必要があります。

于 2010-10-28T19:49:51.303 に答える
0

Dustinの実装は、ターゲットオブジェクトとデリゲートを弱い参照にラップするだけの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);

これにより、インスタンスメソッドの代わりに匿名メソッドを使用することもできます(これは、Dustinの実装の欠点でもあるようです)。

于 2015-02-11T01:11:16.143 に答える
0

重要な詳細:

Dustinの実装は、イベントハンドラーによって導入された強力な参照を排除しますが、新しいメモリリークを導入する可能性があります(少なくとも十分な注意を払っていない場合)。

登録解除コールバックは弱いイベントハンドラーとして扱われないため、オブジェクトへの強い参照が含まれている可能性があります。これは、Eventサブスクライバークラスで登録解除コールバックを宣言するかどうかによって異なります。

そうしない場合、コールバックはそれを囲むクラスインスタンスへの参照に関連付けられます。これが私が参照しているものの例です:登録解除コールバックを宣言した後、それはプログラムクラスインスタンスへの参照を含みます:

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);
    }
}
于 2015-02-11T01:50:37.050 に答える
0

弱いイベントの実装を使用する場合は注意してください。弱いイベントは、イベント(またはメッセージ)の購読を解除する責任を購読者から取り除くように見えます。その場合、サブスクライバーが「スコープ外」になった後でも、イベントハンドラーが呼び出される可能性があります。明示的にサブスクライブを解除せず、ガベージコレクション可能になるが、まだガベージコレクションされていないサブスクライバーを取得します。弱いイベントマネージャーはその状態を検出できず、そのため、そのサブスクライバーのイベントハンドラーを呼び出します。これは、あらゆる種類の予期しない副作用につながる可能性があります。

詳細については、「弱いイベントパターンは危険です」を参照してください。弱いイベントマネージャとしてMvvMCrossメッセージングプラグインを使用してこの問題を説明するこのソースコード
を 参照してください。

于 2015-09-20T03:21:37.453 に答える