2009-07-06 25 views
26

Nếu đối tượng A nghe một sự kiện từ đối tượng B, đối tượng B sẽ giữ đối tượng A còn sống. Có sự triển khai chuẩn của các sự kiện yếu kém có thể ngăn chặn điều này không? Tôi biết WPF có một số cơ chế nhưng tôi đang tìm kiếm một cái gì đó không gắn với WPF. Tôi đoán giải pháp nên sử dụng tài liệu tham khảo yếu ở đâu đó.Sự kiện yếu trong .NET?

+0

Câu hỏi liên quan: http://stackoverflow.com/questions/371109/garbage-collection-when-using-anonymous-delegates-for-event-handling – Benjol

+0

Câu hỏi liên quan khác, với câu trả lời hay hơn: http: // stackoverflow.com/questions/1747235/weak-event-handler-model-for-use-with-lambdas/1747236#1747236 – Benjol

+0

Dự án nguồn mở Sharp Observations http://sharpobservation.codeplex.com/ cung cấp một mục đích chung rất tốt sự kiện yếu/triển khai đại biểu yếu. – Mark

Trả lời

44

Dustin Campbell từ blog DidItWith.NET xem xét một số nỗ lực không thành công để tạo trình xử lý sự kiện yếu, sau đó tiếp tục hiển thị hợp lệ , làm việc, thực hiện nhẹ: Solving the Problem With Weak Event Handlers.

Lý tưởng nhất, mặc dù, Microsoft sẽ giới thiệu khái niệm này vào chính ngôn ngữ đó. Một cái gì đó như:

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

Nếu bạn cảm thấy tính năng này quan trọng với bạn, vui lòng vote for it here.

+7

Ngoài ra còn có một bài viết mã về các sự kiện yếu ở đây http://www.codeproject.com/KB/cs/WeakEvents.aspx – zebrabox

+0

Tôi cũng khuyên bạn nên sử dụng: http://jp-labs.blogspot.com/2009/ 07/enhanced-weak-events-part-two-immutable_30.html Đó là một tối ưu hóa mà tôi đã thực hiện trên các WeakEvents có trong codeproject. – jpbochi

+0

+1, Đó là một đọc thực sự tốt. –

1

Sử dụng recommended Dispose() pattern, nơi bạn xem xét sự kiện tài nguyên được quản lý để dọn sạch, nên xử lý việc này. Đối tượng A nên hủy đăng ký chính nó với tư cách là người nghe các sự kiện từ đối tượng B khi nó được xử lý ...

+1

Cảm ơn bạn đã trả lời. Tuy nhiên, bạn đang trả lời một câu hỏi khác. Mẫu vứt bỏ không thay thế cho các tham chiếu/sự kiện yếu. Nó không phải là một giải pháp cho vấn đề của tôi. – tom7

+0

Tôi có thể yêu cầu ai đó "ngắt kết nối phương thức này trên đối tượng A khỏi mọi sự kiện mà nó đang nghe" hay tôi phải giữ tham chiếu đến đối tượng B? – sisve

+0

Bạn phải giữ tham chiếu và xóa nó khỏi danh sách yêu cầu bằng tay. –

10

Tôi đóng gói lại việc triển khai Dustin Campbell để giúp việc mở rộng nó dễ dàng hơn đối với các loại sự kiện khác nhau khi các trình xử lý chung không được sử dụng. Tôi đoán nó có thể là một số sử dụng cho một ai đó.

Điểm Uy Tín:
Mr. Campbell's original implementation
Một chức năng đại diện dàn diễn viên rất tiện dụng bởi Ed Ball, liên kết có thể được tìm thấy trong nguồn

Việc xử lý và một vài quá tải, EventHander < E> và 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; 
    } 
} 
} 

Bài kiểm tra đơn vị:


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

Đây là giải pháp tôi đã chọn! Đã cho tôi một số rắc rối để lắp ráp tất cả mọi thứ, nhưng cho đến nay nó mang lại cho tôi không có rắc rối. +1 – Joel

0

Dustin của chỉ làm việc với các đại biểu EventHandler. Nếu bạn đi đến CodePlex, có một dự án được gọi là Sharp Observation, trong đó tác giả đã xây dựng một nhà cung cấp ủy quyền yếu rất tốt. Nó được thực hiện trong MSIL và nhanh hơn và linh hoạt hơn đáng kể.

... trong đó, cho đến khi Microsoft triển khai các sự kiện yếu một cách nguyên bản, sẽ phải thực hiện.

0

lợi thế gì thực hiện Dustin của đã so với lớp WeakEventManager của WPF mà chỉ đơn giản kết thúc tốt đẹp các đối tượng mục tiêu cũng như các đại biểu thành một tài liệu tham khảo yếu:

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

Theo tôi phương pháp này là linh hoạt hơn, vì nó không yêu cầu triển khai để truyền đối tượng đích dưới dạng tham số trong khi gọi trình xử lý sự kiện:

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

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

Điều này cũng cho phép sử dụng các phương pháp bất thường thay vì một phương pháp thể hiện (có vẻ cũng là một bất lợi của việc thực hiện Dustin).

0

Một chi tiết quan trọng:

thực hiện Dustin của loại bỏ một tài liệu tham khảo mạnh mẽ được giới thiệu bởi các xử lý sự kiện, nhưng nó có thể giới thiệu một rò rỉ bộ nhớ mới (ít nhất là khi không chú ý đến đủ).

Vì cuộc gọi lại không đăng ký không được coi là trình xử lý sự kiện yếu nên nó có thể chứa tham chiếu mạnh mẽ đối với một số đối tượng. Nó phụ thuộc vào việc bạn khai báo gọi lại unregister trong lớp người đăng ký sự kiện hay không.

Nếu không làm như vậy, cuộc gọi lại sẽ được liên kết với tham chiếu đến cá thể lớp kèm theo. Dưới đây là một ví dụ về những gì tôi đang đề cập đến: Sau khi tuyên bố callback unregister nó sẽ chứa một tham chiếu đến thể hiện đẳng cấp Chương trình:

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

Hãy cẩn thận khi sử dụng các sự kiện yếu triển khai. Sự kiện yếu xuất hiện để xóa khỏi người đăng ký có trách nhiệm hủy đăng ký khỏi sự kiện (hoặc tin nhắn). Trong trường hợp đó, trình xử lý sự kiện có thể được gọi ngay cả sau khi người đăng ký "đi ngoài phạm vi". Hãy thuê bao mà không hủy đăng ký một cách rõ ràng và trở thành rác thu thập được nhưng vẫn chưa được thu gom rác. Trình quản lý sự kiện yếu sẽ không thể phát hiện trạng thái đó và do đó nó sẽ vẫn gọi trình xử lý sự kiện của người đăng ký đó. Điều này có thể dẫn đến tất cả các loại tác dụng phụ không mong muốn.

Xem thêm chi tiết tại The Weak Event Pattern is Dangerous.
Xem điều này source code minh họa sự cố này bằng cách sử dụng plugin nhắn tin MvvMCross làm trình quản lý sự kiện yếu.

Các vấn đề liên quan