gpt4 book ai didi

c# - 清理事件处理程序引用的最佳做法是什么?

转载 作者:IT王子 更新时间:2023-10-29 04:09:25 24 4
gpt4 key购买 nike

我经常发现自己写的代码是这样的:

        if (Session != null)
{
Session.KillAllProcesses();
Session.AllUnitsReady -= Session_AllUnitsReady;
Session.AllUnitsResultsPublished -= Session_AllUnitsResultsPublished;
Session.UnitFailed -= Session_UnitFailed;
Session.SomeUnitsFailed -= Session_SomeUnitsFailed;
Session.UnitCheckedIn -= Session_UnitCheckedIn;
UnattachListeners();
}

目的是清除我们在目标(Session)上注册的所有事件订阅,以便 GC 可以自由处置 Session。我与一位同事讨论了实现 IDisposable 的类,但他认为这些类应该像这样执行清理:

    /// <summary>
/// Disposes the object
/// </summary>
public void Dispose()
{
SubmitRequested = null; //frees all references to the SubmitRequested Event
}

是否有偏爱其中一个的理由?有没有更好的方法来解决这个问题? (除了无处不在的弱引用事件)

我真正希望看到的是类似于引发事件的安全调用模式的东西:即安全且可重复。每次我附加到一个事件时,我都会记得做一些事情,这样我就可以确保我很容易清理它。

最佳答案

Session 事件中注销处理程序将以某种方式允许 GC 收集 Session 对象的说法是不正确的。这是一个说明事件引用链的图表。

--------------      ------------      ----------------
| | | | | |
|Event Source| ==> | Delegate | ==> | Event Target |
| | | | | |
-------------- ------------ ----------------

因此在您的情况下,事件源是一个 Session 对象。但我没有看到你提到哪个类声明了处理程序,所以我们还不知道事件目标是谁。让我们考虑两种可能性。事件目标可以是表示源的同一 Session 对象,也可以是一个完全独立的类。在任何一种情况下,在正常情况下,只要没有其他引用,就会收集 Session,即使其事件的处理程序保持注册状态也是如此。那是因为委托(delegate)不包含对事件源的引用。它仅包含对事件目标的引用。

考虑以下代码。

public static void Main()
{
var test1 = new Source();
test1.Event += (sender, args) => { Console.WriteLine("Hello World"); };
test1 = null;
GC.Collect();
GC.WaitForPendingFinalizers();

var test2 = new Source();
test2.Event += test2.Handler;
test2 = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}

public class Source()
{
public event EventHandler Event;

~Source() { Console.WriteLine("disposed"); }

public void Handler(object sender, EventArgs args) { }
}

您将看到“disposed”被打印到控制台两次,以验证这两个实例都已在未注销事件的情况下被收集。 test2 引用的对象被收集的原因是因为它在引用图中仍然是一个孤立的实体(一旦 test2 设置为 null),即使它有一个引用通过事件回到自身。

现在,当您希望事件目标的生命周期比事件源的生命周期短时,事情就变得棘手了。在那种情况下,您必须取消注册事件。请考虑以下演示这一点的代码。

public static void Main()
{
var parent = new Parent();
parent.CreateChild();
parent.DestroyChild();
GC.Collect();
GC.WaitForPendingFinalizers();
}

public class Child
{
public Child(Parent parent)
{
parent.Event += this.Handler;
}

private void Handler(object sender, EventArgs args) { }

~Child() { Console.WriteLine("disposed"); }
}

public class Parent
{
public event EventHandler Event;

private Child m_Child;

public void CreateChild()
{
m_Child = new Child(this);
}

public void DestroyChild()
{
m_Child = null;
}
}

您会看到“disposed”永远不会打印到控制台,表明可能存在内存泄漏。这是一个特别难处理的问题。在 Child 中实现 IDisposable 并不能解决问题,因为无法保证调用方会很好地播放并实际调用 Dispose

答案

如果您的事件源实现了 IDisposable,那么您实际上并没有给自己买任何新东西。这是因为如果事件源不再是根,那么事件目标也将不再是根。

如果您的事件目标实现了 IDisposable,那么它可以从事件源中清除自身,但不保证 Dispose 会被调用。

我并不是说从 Dispose 中注销事件是错误的。我的观点是,您确实需要检查您的类层次结构是如何定义的,并考虑如何最好地避免内存泄漏问题(如果存在的话)。

关于c# - 清理事件处理程序引用的最佳做法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3258064/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com