是否有必要显式删除C#中的事件处理程序

我有一个class提供了一些事件。 这个类是全局声明的,但没有在全局声明中实例化 – 它是在需要的方法中根据需要实例化的。

每次在方法中需要该类时,它都是实例化的,并且注册了事件处理程序。 在方法超出范围之前,是否有必要显式移除事件处理程序?

当方法超出范围时,类的实例也是如此。 是否将事件处理程序注册到正在超出范围的实例中是否具有内存占用量的含义? (我想知道如果事件处理程序保持GC看到类实例不再被引用。)

谢谢。

在你的情况下,一切都很好。 这是发布事件的对象,它保持事件处理程序的目标 。 所以如果我有:

publisher.SomeEvent += target.DoSomething; 

那么publisher有一个参考target而不是相反的方向。

在你的情况下,发布者将有资格进行垃圾收集(假设没有其他引用),所以事件处理程序目标的引用是不相关的。

棘手的情况是,当发布者是长期的,但用户不想成为 – 在这种情况下,你需要取消订阅处理程序。 例如,假设您有一些数据传输服务,它允许您订阅有关带宽更改的asynchronous通知,而传输服务对象是长期的。 如果我们这样做:

 BandwidthUI ui = new BandwidthUI(); transferService.BandwidthChanged += ui.HandleBandwidthChange; // Suppose this blocks until the transfer is complete transferService.Transfer(source, destination); // We now have to unsusbcribe from the event transferService.BandwidthChanged -= ui.HandleBandwidthChange; 

(你实际上想要使用finally块来确保你不会泄漏事件处理程序。)如果我们没有取消订阅,那么BandwidthUI将至less和传输服务一样长。

就我个人而言,我很less遇到这种情况 – 通常如果我订阅一个事件,那么这个事件的目标至less和发布者一样长 – 例如,一个表单将持续与其上的button一样长。 值得了解这个潜在的问题,但是我觉得有些人不需要担心,因为他们不知道参考文献的位置。

编辑:这是回答乔纳森·迪金森的评论。 首先,查看Delegate.Equals(object)的文档,明确地给出了平等的行为。

其次,这里是一个简短而完整的程序来显示取消订阅工作:

 using System; public class Publisher { public event EventHandler Foo; public void RaiseFoo() { Console.WriteLine("Raising Foo"); EventHandler handler = Foo; if (handler != null) { handler(this, EventArgs.Empty); } else { Console.WriteLine("No handlers"); } } } public class Subscriber { public void FooHandler(object sender, EventArgs e) { Console.WriteLine("Subscriber.FooHandler()"); } } public class Test { static void Main() { Publisher publisher = new Publisher(); Subscriber subscriber = new Subscriber(); publisher.Foo += subscriber.FooHandler; publisher.RaiseFoo(); publisher.Foo -= subscriber.FooHandler; publisher.RaiseFoo(); } } 

结果:

 Raising Foo Subscriber.FooHandler() Raising Foo No handlers 

(在Mono和.NET 3.5SP1上testing)

进一步编辑:

这是为了certificate一个事件发布者可以被收集,同时还有一个订阅者的引用。

 using System; public class Publisher { ~Publisher() { Console.WriteLine("~Publisher"); Console.WriteLine("Foo==null ? {0}", Foo == null); } public event EventHandler Foo; } public class Subscriber { ~Subscriber() { Console.WriteLine("~Subscriber"); } public void FooHandler(object sender, EventArgs e) {} } public class Test { static void Main() { Publisher publisher = new Publisher(); Subscriber subscriber = new Subscriber(); publisher.Foo += subscriber.FooHandler; Console.WriteLine("No more refs to publisher, " + "but subscriber is alive"); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("End of Main method. Subscriber is about to " + "become eligible for collection"); GC.KeepAlive(subscriber); } } 

结果(在.NET 3.5SP1中,Mono在这里显得有点奇怪,稍后会研究一下):

 No more refs to publisher, but subscriber is alive ~Publisher Foo==null ? False End of Main method. Subscriber is about to become eligible for collection ~Subscriber 

在你的情况下,你很好。 我原本是把你的问题倒退了,一个订阅者超出了范围,而不是出版商 。 如果事件发布者超出了范围,那么对订阅者的引用 (当然不是订阅者本身!)随之而去,并且不需要明确地删除它们。

我的原始答案如下,关于如果您创build一个事件订阅者,并让它在没有取消订阅的情况下超出范围会发生什么。 它不适用于你的问题,但我会留下它的历史。

如果类仍然通过事件处理程序注册,那么它仍然可以访问。 它仍然是一个活的对象。 跟随事件graphics的GC会发现它已连接。 是的,你会想明确地删除事件处理程序。

仅仅因为对象超出了原来的分配范围,并不意味着它是GC的候选对象。 只要现场参考依然存在。