我试图了解事件如何导致内存泄漏。我在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窗体应用程序中使用此类,如下所示。
public partial class Form1 : Form
{
Person John = new Person() { LastName = "Doe", FirstName = "John" };
public Form1()
{
InitializeComponent();
John.UponWakingUp += new EventHandler(John_UponWakingUp);
}
void John_UponWakingUp(object sender, EventArgs e)
{
Console.WriteLine("John is waking up");
}
private void button1_Click(object sender, EventArgs e)
{
John = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
MessageBox.Show("done");
}
}
如您所见,我实现了Person类并订阅了UponWakingUp事件。我在这张表格上有一个按钮。当用户单击此按钮时,我将此Person实例设置为null,而不取消订阅该事件。然后我调用GC.Collect以确保已执行Garbade集合。我在这里显示一个消息框,以便我可以附加Windbg来查看Form1类的引用帮助,并且在这个类中我没有看到任何对该事件的引用(尽管Form1的数据太长,我显示的Windbg输出如下所示)与我的问题有关)。此类具有对Person类的引用,但它为null。基本上这对我来说似乎不是一个内存泄漏,因为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
答案 0 :(得分:5)
这是循环引用的情况。表单引用了通过 John 字段侦听事件的对象。反过来,John在表单的构造函数订阅了UponWakingUp事件时引用了表单。
循环引用在某些自动内存管理方案中可能是个问题,特别是在引用计数方面。但.NET垃圾收集器并没有问题。只要表单对象和Person对象都没有任何附加引用,两者之间的循环引用就不能保持对方的活动。
您的代码中没有其他引用。这通常会导致两个对象被垃圾收集。但Form类是特殊的,只要存在本机Windows窗口,存储在由Winforms维护的句柄到对象表中的内部引用就会使表单对象保持活动状态。这使约翰活着。
因此,清理它的正常方法是用户通过单击右上角的X来关闭窗口。这反过来导致本机窗口句柄被破坏。这将从该内部表中删除表单引用。下一个垃圾收集现在只看到循环引用,并收集它们。
答案 1 :(得分:4)
答案实际上就是你所关联的问题的答案:
当侦听器将事件侦听器附加到事件时,源 object将获取对侦听器对象的引用。这意味着 之前,垃圾收集器无法收集侦听器 分离事件处理程序,或收集源对象。
您正在发布来源对象(Person
),因此可以收集监听器(您的Form
),这就是为什么没有内存泄漏。
当这种情况与I.E.相反时会发生内存泄漏。如果您要处置Form
,但事件来源(您的Person
对象)仍然存在,并持有对它的引用。