将所有标准事件处理程序替换为 WeakEventManager 或其变体是否安全?

Yoh*_*hyo 5 .net c# wpf event-handling weak-events

标准事件处理程序(带有运算符+=)是内存泄漏的原因之一(如果它没有被取消注册/处置(带有-=运算符))。

微软用WeakEventManager它的继承方式解决了这个问题:PropertyChangedEventManager, CollectionChangedEventManager, CurrentChangedEventManager, ErrorsChangedEventManager等等。

内存泄漏的简单示例代码是:

public class EventCaller
{
    public static event EventHandler MyEvent;
    
    public static void Call()
    {
        var handler = MyEvent;
        if (handler != null)
        {
            handler(null, EventArgs.Empty);
            Debug.WriteLine("=============");
        }
    }
}

public class A
{
    string myText;

    public A(string text)
    {
        myText = text;
        EventCaller.MyEvent += OnCall;

        // Use code below and comment out code above to avoid memory leakage.
        // System.Windows.WeakEventManager<EventCaller, EventArgs>.AddHandler(null, "MyEvent", OnCall);  
    }
    
    void OnCall(object sender, EventArgs e)
    {
        Debug.WriteLine(myText);
    }
    
    ~A()
    {
        Debug.WriteLine(myText + " destructor");
    }
}

void Main()
{
    var a = new A("A");
    var b = new A("B");
    EventCaller.Call();
    a = null;
    GC.Collect();
    EventCaller.Call();
}
Run Code Online (Sandbox Code Playgroud)

输出是:

A
B
+++++++
A
B
+++++++
Run Code Online (Sandbox Code Playgroud)

我们可以看到析构函数不会被调用。但是如果我们改变(通过注释未使用的代码):

    EventCaller.MyEvent += OnCall;
Run Code Online (Sandbox Code Playgroud)

    System.Windows.WeakEventManager<EventCaller, EventArgs>.AddHandler(null, "MyEvent", OnCall);  
Run Code Online (Sandbox Code Playgroud)

输出是:

A
B
+++++++
B
+++++++
A destructor
B destructor
Run Code Online (Sandbox Code Playgroud)

在 A 被清空之后,它的事件处理程序将不再被调用。A 和 B 不再使用后将被处理,无需-=操作员。

  1. 我可以安全地替换所有 += 运算符,System.Windows.WeakEventManager以避免由于可能丢失事件取消注册和保存代码而导致的内存泄漏不应实现IDisposable吗?

  2. 如果它不是真的安全,我应该考虑或注意什么?

mm8*_*mm8 6

我可以安全地替换所有 += 运算符,System.Windows.WeakEventManager以避免由于可能丢失事件取消注册而导致内存泄漏,并通过不应实现 IDisposable 保存代码吗?

你可以吗?大概。你应该?可能不会。如果您确实对事件处理程序有强引用,那么如果事件的发布者比事件的订阅者生存时间更长,您应该更愿意取消订阅它,而不是用弱事件替换强引用。使用弱事件有副作用。其中之一是性能。另一个是语义上的差异。关于为什么 .NET Framework 中的事件实现默认不使用弱事件模式,您可能需要参考以下问题和解答:

为什么 C# 中的事件实现默认不使用弱事件模式?

当然,在某些情况下您应该使用弱事件模式。其中一种场景是 WPF 中的数据绑定,其中源对象完全独立于侦听器对象。但这并不意味着您应该始终使用弱事件模式。这也不意味着您应该停止关心在应用程序中处理订阅。