错误的说法是从Session
事件中注销处理程序会以某种方式允许Session
对象被GC收集。下面是一张图解释了事件的引用链。
| | | | | |
|Event Source| ==> | Delegate | ==> | Event Target |
| | | | | |
在您的情况下,事件源是一个Session
对象。但我没有看到您提到哪个类声明了处理程序,因此我们还不知道事件目标是谁。让我们考虑两种可能性。事件目标可以是表示源的相同Session
对象,也可以是完全不同的类。在任何情况下,在正常情况下,只要没有其他引用,即使其事件的处理程序保持注册状态,Session
也将被收集。这是因为委托不包含对事件源的引用。它只包含对事件目标的引用。
考虑以下代码。
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
中注销事件是错误的。我的观点是你真正需要检查你的类层次结构的定义,并考虑如何尽可能避免内存泄漏问题(如果存在)。