在C#中有必要明确删除事件处理程序吗?
在C#中有必要明确删除事件处理程序吗?
我有一个类提供一些事件。这个类是全局声明但不是全局实例化的-它在需要它的方法中按需实例化。每次在方法中需要这个类时,都会实例化并注册事件处理程序。在方法退出作用域之前,是否需要显式删除事件处理程序?当方法退出作用域时,类的实例也会退出作用域。在这个正在退出作用域的实例保留事件处理程序是否有内存印象呢?(我想知道事件处理程序是否防止GC认为该类实例已不再被引用。)
在你的情况下,一切都很好。发布事件的对象会保持事件处理程序的目标存活。所以如果我有:
publisher.SomeEvent += target.DoSomething;
那么publisher
是对target
的引用,但反过来则不成立。
在你的情况下,发布者将被收集垃圾(假设没有其他对其的引用),因此它引用事件处理程序的目标是无关紧要的。
麻烦的情况是当发布器长寿而订户不想要时 - 在这种情况下,您需要取消订阅处理程序。例如,假设您有一些数据传输服务,让您订阅有关带宽更改的异步通知,而传输服务对象则存活时间较长。如果我们这样做:
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
至少会与传输服务一样长。
个人而言,我很少遇到这种情况 - 通常,如果我订阅了事件,该事件的目标至少与发布者一样长 - 例如,一个窗体将持续与其上的按钮一样长。值得了解此潜在问题,但我认为有些人会过多担心,因为他们不知道引用的方向。
编辑:这是为了回答Jonathan Dickinson的评论。首先,查看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上测试。)
进一步编辑:
这是为了证明在仍然存在对订户的引用时,事件发布者可以被收集。
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