2009-07-06 8 views
26

Wenn Objekt A auf ein Ereignis von Objekt B hört, hält Objekt B Objekt A am Leben. Gibt es eine Standardimplementierung von schwachen Ereignissen, die dies verhindern würden? Ich weiß, dass WPF einen Mechanismus hat, aber ich suche nach etwas, das nicht an WPF gebunden ist. Ich vermute, die Lösung sollte irgendwo schwache Referenzen verwenden.Schwache Ereignisse in .NET?

+0

In Verbindung stehende Frage: http://stackoverflow.com/questions/371109/garbage-collection-when-using-anonymous-delegates-for-event-handling – Benjol

+0

Eine weitere verwandte Frage, mit einer besseren Antwort: http: // stackoverflow.com/questions/1747235/weak-event-handler-model-for-use-with-lambdas/1747236#1747236 – Benjol

+0

Die Open-Source-Projekt Sharp Beobachtungen http://sharpobservation.codeplex.com/ bietet eine sehr gute allgemeine Zwecke Implementierung eines schwachen Ereignisses/Delegierten. – Mark

Antwort

44

Dustin Campbell aus dem Blog DidItWith.NET untersucht einige der fehlgeschlagenen Versuche, schwache Event-Handler zu erstellen, und zeigt dann eine gültige , arbeiten, leichte Implementierung: Solving the Problem With Weak Event Handlers.

Idealerweise aber wäre Microsoft das Konzept in die Sprache selbst einzuführen. Etwas wie:

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

Wenn Sie glauben, diese Funktion für Sie wichtig ist, bitte vote for it here.

+7

Es gibt auch einen Code Codeproject Artikel über schwache Ereignisse hier http://www.codeproject.com/KB/cs/WeakEvents.aspx – zebrabox

+0

Ich würde dies empfehlen: http://jp-labs.blogspot.com/2009/ 07/enhanced-weak-events-part-two-immutable_30.html Es ist eine Optimierung, die ich auf den WeekEvents gemacht habe, die in Codeprojekt sind. – jpbochi

+0

+1, Das war eine wirklich, wirklich gute Lektüre. –

1

die recommended Dispose() pattern Verwendung, wo Sie Ereignisse eine verwaltete Ressource betrachten, um aufzuräumen, sollte dies handhaben. Objekt A sollte sich selbst als Listener von Ereignissen von Objekt B abmelden, wenn es entsorgt wird ...

+1

Vielen Dank für Ihre Antwort. Sie beantworten jedoch eine andere Frage. Das Dispo-Muster ist kein Ersatz für schwache Referenzen/Ereignisse. Es ist keine Lösung für mein Problem. – tom7

+0

Kann ich jemandem sagen, dass er "diese Methode für Objekt A von jedem Ereignis trennen soll, das er hört", oder muss ich einen Verweis auf Objekt B behalten? – sisve

+0

Sie müssen den Verweis beibehalten und manuell aus den Aufruflisten entfernen. –

10

I neu verpackt Dustin Campbell Implementierung es ein wenig leichter zu machen, es für verschiedene Ereignistypen zu verlängern, wenn generische Handler nicht verwendet werden. Ich denke, es könnte jemandem von Nutzen sein.

Credits:
Mr. Campbell's original implementation
Eine sehr handliche Delegat Umsetzungsfunktion von Ed Ball, kann Link in der Quelle

Der Handler und ein paar Überlastungen, EventHander < E> und PropertyChangedEventHandler finden:


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

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

Dies ist die Lösung, die ich gewählt habe! Gab mir Schwierigkeiten, alles zusammenzustellen, aber bis jetzt gibt es mir keine Probleme. +1 – Joel

0

Dustin Implementierung nur mit Eventhandler Delegierten arbeitet. Wenn Sie zu CodePlex gehen, gibt es ein Projekt namens Sharp Observation, in dem der Autor einen sehr guten schwachen Delegiertenanbieter aufgebaut hat. Es ist in MSIL implementiert und ist wesentlich schneller und flexibler.

..., die bis Microsoft native schwache Ereignisse implementieren, zu tun haben.

0

Welche Vorteile hat Dustin Implementierung haben die WeakEventManager Klasse WPF verglichen, die einfach als auch die Delegierten in eine schwache Referenz das Zielobjekt hüllt:

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

Meiner Meinung nach ist dieser Ansatz flexibler, da die Implementierung die Zielinstanz während des Aufrufs des Ereignishandlers nicht als Parameter übergeben muss:

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

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

Dies erlaubt auch die Verwendung von anomymischen Methoden anstelle einer Instanzmethode (was auch ein Nachteil der Dustin-Implementierung zu sein scheint).

0

Ein wichtiges Detail:

Dustin Implementierung beseitigt eine starke Referenz durch die Event-Handler eingeführt, aber es kann einen neuen Speicherverlust einführen (zumindest, wenn sie nicht genug Aufmerksamkeit zu zahlen).

Da der austragen Rückruf nicht als schwacher Ereignishandler behandelt wird es einen starken Bezug auf ein Objekt enthält. Dies hängt davon ab, ob Sie den Rückruf für die Registrierung in der Abonnentenklasse "Event" deklarieren oder nicht.

Wenn dies nicht zu tun, wird der Rückruf mit einem Verweis auf die umgebenden Klasse Instanz zugeordnet werden. Hier ist ein Beispiel dafür, was ich beziehe zu: Nach dem austragen Rückruf erklärt es einen Verweis auf die Programm Klasseninstanz enthalten:

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

Vorsicht bei schwachen Ereignissen Implementierungen verwenden. Schwache Ereignisse scheinen dem Abonnenten die Verantwortung zu entziehen, sich von dem Ereignis (oder der Nachricht) abzumelden. In diesem Fall können Event-Handler aufgerufen werden, auch wenn der Abonnent "außerhalb des Gültigkeitsbereichs" ist. Nehmen Sie einen Abonnenten, der nicht explizit abbestellt wird und der zu Garbage Collectable wird, aber noch nicht Garbage Collection gesammelt wird. Der Manager für schwache Ereignisse kann diesen Status nicht erkennen und ruft deshalb immer noch den Ereignishandler dieses Abonnenten auf. Dies kann zu allen unerwarteten Nebenwirkungen führen.

Weitere Details zu The Weak Event Pattern is Dangerous.
Sehen Sie diese source code, die dieses Problem mit der MvvMCross Messaging-Plugin als schwacher Event-Manager zeigt.