我认为这是一个C#初学者问题,但我似乎无法找到正确的解决方案。
我有一个ClassOne对象,它定义了一个事件。 我创建了一个ClassTwo对象,它被认为是一个黑盒子,这意味着我不知道它是否会注册到任何事件。 ClassTwo构造函数注册到ClassOne的事件。 当ClassTwo对象超出范围时,问题就出现了。垃圾收集器永远不会删除此对象,因为它从未取消注册该事件。
所以我有两个问题:
当ClassTwo对象超出范围时,有没有办法知道?对于一个旧的C ++程序员来说,这将在析构函数中,但是使用C#这不起作用。
是否有调试工具可以帮助我找到这样的对象?
以下是重现问题的示例代码:
public partial class MainWindow : Window
{
static public ClassOne classOne = new ClassOne();
public MainWindow()
{
InitializeComponent();
ClassTwo classtwo = new ClassTwo();
}
private void buttonTest_Click(object sender, RoutedEventArgs e)
{
GC.Collect();
}
}
public class ClassOne
{
public ClassOne()
{
Trace.WriteLine(this + " constructor");
}
~ClassOne()
{
Trace.WriteLine(this + " destructor");
}
public delegate void UpdateFunc(object sender, EventArgs args);
public event UpdateFunc OnUpdate;
}
public class ClassTwo
{
public ClassTwo()
{
Trace.WriteLine(this + " constructor");
MainWindow.classOne.OnUpdate += new ClassOne.UpdateFunc(classOne_OnUpdate);
}
void classOne_OnUpdate(object sender, EventArgs args)
{
throw new NotImplementedException();
}
~ClassTwo()
{
Trace.WriteLine(this + " destructor");
}
}
答案 0 :(得分:6)
我会在这样的对象上实现IDisposable
,并从Dispose
方法中的事件中取消注册。
你会像这样使用你的对象:
using(var two = new ClassTwo(classOne))
{
// Do something with two
}
// object can now be garbage collected.
如果来电者未能拨打Dispose
,那你就不走运了。
答案 1 :(得分:5)
除非它实现 IDisposable
和 ,否则调用者会通过正确调用Dispose
进行合作。
(当然,为什么呼叫者不合作?)
不是我的意思。 :(我认为你最好的办法是实施IDisposable
并在Dispose
取消注册。
答案 2 :(得分:0)
正如其他人所提到的,Dispose模式是解决此问题的方法。如果ClassTwo对象存在很短的时间,您可以使用C#using语句来确保在使用完对象后调用Dispose:
using (var foo = new ClassTwo())
{
foo.Bar();
}
为了找到此类问题的根本原因,您需要使用内存分析器。 dotTrace已被提及,另一个好的是SciTech Memory Profiler。你需要找到的是你认为应该被垃圾收集的对象的根路径,但事实并非如此。根路径是未收集对象的原因 - 因为通过传递引用,保证活着的对象(GC Root)引用您想要死的对象。
这些内存分析器非常有助于识别导致您出现问题的GC根,以及从根到对象的引用路径。沿着该根路径的某个位置将是不合适的引用,并且是该问题的原因。
在您的代码中,未收集ClassTwo对象的原因可能是MainWindow具有对ClassOne对象的静态引用,或者从ClassOne到ClassTwo的事件处理程序尚未取消挂钩。静态引用是GC Roots的一个示例 - 所以在MainWindow中的静态引用发生更改之前,所有classOne引用都将处于活动状态。
静态或事件处理程序是否存在问题取决于应用程序的场景 - 是否也应该收集ClassOne对象 - 是否有静态引用它?或者是静态引用所需的行为 - classOne是一个长期存在的对象,classTwo是一个短命的对象,在这种情况下,classTwo应该在它的生命结束时处置,并且Dispose应该取消挂起事件处理程序。
这是一篇关于.Net GC的好文章,由Jeffrey Richter撰写:http://msdn.microsoft.com/en-us/magazine/bb985010.aspx。它现在有点老了,近年来GC已经有了新增功能,但它是一个很好的起点。