2013-01-20 31 views
5

我想了解事件如何导致内存泄漏。我在this的stackoverflow问题中发现了一个很好的解释,但是当在Windg中查看对象时,我对结果感到困惑。首先,我有一个简单的课程,如下所示。为什么这不会导致内存泄漏事件未取消订阅

class Person 
    { 
     public string LastName { get; set; } 
     public string FirstName { get; set; } 

     public event EventHandler UponWakingUp; 
     public Person() { } 

     public void Wakeup() 
     { 
      Console.WriteLine("Waking up"); 
      if (UponWakingUp != null) 
       UponWakingUp(null, EventArgs.Empty); 
     } 
    } 

现在我在Windows窗体应用程序中使用这个类,如下所示。你可以看到,我instaniated人类和订阅UponWakingUp事件。我在这个表单上有一个按钮。当用户点击这个按钮时,我将这个Person实例设置为null,而不用取消订阅该事件。然后我打电话给GC.Collect以确保Garbade系列已经完成。我在此处显示一个消息框,以便我可以将Windbg附加到Form1类的引用帮助中,并且在此类中我没有看到对该事件的任何引用(Windbg输出如下所示,尽管Form1的数据太长,我正在显示与我的问题有关)。这个类有一个Person类的引用,但它是空的。基本上,这似乎不像内存泄漏给我,因为Form1没有任何对Person类的引用,即使它没有退订该事件。

我的问题是如果这确实会导致内存泄漏。如果不是,为什么不呢?

0:005> !do 0158d334 
Name:  WindowsFormsApplication1.Form1 
MethodTable: 00366390 
EEClass:  00361718 
Size:  332(0x14c) bytes 
File:  c:\Sandbox\\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe 
Fields: 
     MT Field Offset     Type VT  Attr Value Name 
619af744 40001e0  4  System.Object 0 instance 00000000 __identity 
60fc6c58 40002c3  8 ...ponentModel.ISite 0 instance 00000000 site 
619af744 4001534  b80  System.Object 0 static 0158dad0 EVENT_MAXIMIZEDBOUNDSCHANGED 
**00366b70 4000001  13c ...plication1.Person 0 instance 00000000 John** 
60fc6c10 4000002  140 ...tModel.IContainer 0 instance 00000000 components 
6039aadc 4000003  144 ...dows.Forms.Button 0 instance 015ad06c button1 

0:008> !DumpHeap -mt 00366b70  
Address  MT  Size 
total 0 objects 
Statistics: 
     MT Count TotalSize Class Name 
Total 0 objects 

回答

5

这是一个循环参考案例。该表单具有对通过约翰字段监听事件的对象的引用。反过来,当它的形式的构造函数订阅了它的UponWakingUp事件时,约翰也会引用表单。

循环引用可能是某些自动内存管理方案中的问题,特别是在引用计数中。但是.NET垃圾收集器没有问题。只要表单对象和Person对象都没有任何附加引用,那么两者之间的循环引用就不能保持彼此活动。

在代码中没有其他引用。这通常会导致这两个对象被垃圾收集。但是Form类是特殊的,只要存在一个本地Windows窗口,存储在由Winforms维护的句柄 - 对象表中的内部引用就会使表单对象保持活动状态。这使约翰活着。

因此,这种清理的常规方式是用户通过单击右上角的X来关闭窗口。这又导致本地窗口句柄被销毁。它从该内部表中删除表单引用。接下来的垃圾收集现在只看到循环引用并收集它们。

4

其实答案在问题的答案已链接到:

当听众附加一个事件侦听器的事件,源 对象将获得听者的引用目的。这意味着 听众不能被垃圾收集器收集,直到或者 事件处理程序被分离,或者收集源对象

你释放对象(Person),所以监听(您Form)是确定要收集这就是为什么没有内存泄漏。

当这种情况与I.E相反时,会发生内存泄漏。当你想处置Form,但事件(你的Person对象)仍然活着持有对它的引用。

+0

在这种情况下,Form1是Person类实例的源代码的列表器。你是说约翰会参考Form1?我现在完全困惑。 –

+0

@palmsnow是的,'John.UponWakingUp'包含对包含对“Form1”的引用的委托的引用。否则提高这个事件是行不通的。 – svick

+0

请参阅[本](http://csharpindepth.com/Articles/Chapter2/Events.aspx)文章以获取更多详细信息。顺便提一下,一个结构良好的问题+1。 –