我有一个提供一些活动的课程.该类在全局声明但未在该全局声明上实例化 - 它根据需要在需要它的方法中实例化.
每次在方法中需要该类时,都会对其进行实例化并注册事件处理程序.是否有必要在方法超出范围之前显式删除事件处理程序?
当方法超出范围时,类的实例也是如此.是否在超出范围的情况下向该实例注册的事件处理程序是否存在内存占用意义?(我想知道事件处理程序是否使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
它将至少与转移服务一样长.
就个人而言,我很少遇到这个 - 通常如果我订阅了一个事件,那个事件的目标至少和发布者一样长 - 例如,一个表单将持续与其上的按钮一样长.值得了解这个潜在问题,但我认为有些人在不需要的时候会担心,因为他们不知道参考文献的哪个方向.
编辑:这是回答乔纳森迪金森的评论.首先,查看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
在你的情况下,你很好.我最初向后看了你的问题,订阅者超出范围,而不是发布者.如果事件发布者超出范围,那么对订阅者的引用(当然不是订阅者本身!)随之而来,并且不需要显式删除它们.
我的原始答案如下,关于如果您创建一个事件订阅者并且在没有取消订阅的情况下让它超出范围会发生什么.它不适用于您的问题,但我会将其保留在历史记录中.
如果类仍然通过事件处理程序注册,那么它仍然可以访问.它仍然是一个活的对象.事件图表后面的GC会发现它已连接.是的,您需要显式删除事件处理程序.
仅仅因为该对象超出其原始分配范围并不意味着它是GC的候选对象.只要现场参考仍然存在,它就是现场直播.