是否有必要在 C# 中显式删除事件处理程序
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/506092/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it necessary to explicitly remove event handlers in C#
提问by rp.
I have a class that offers up a few events. That class is declared globally but not instanced upon that global declaration--it's instanced on an as-needed basis in the methods that need it.
我有一堂课提供了一些活动。该类是全局声明的,但未在该全局声明上实例化——它在需要它的方法中根据需要实例化。
Each time that class is needed in a method, it is instanced and event handlers are registered. Is it necessary to remove the event handlers explicitly before the method goes out of scope?
每次在方法中需要该类时,都会对其进行实例化并注册事件处理程序。是否有必要在方法超出范围之前显式删除事件处理程序?
When the method goes out of scope, so goes the instance of the class. Does leaving event handlers registered with that instance that is going out of scope have a memory footprint implication? (I'm wondering if the event handler keeps the GC from seeing the class instance as no longer being referenced.)
当方法超出范围时,类的实例也会超出范围。离开该超出范围的实例注册的事件处理程序是否会影响内存占用?(我想知道事件处理程序是否让 GC 不再看到类实例不再被引用。)
采纳答案by Jon Skeet
In your case, everything is fine. It's the object which publishesthe events which keeps the targetsof the event handlers live. So if I have:
在你的情况下,一切都很好。它是发布事件的对象,使事件处理程序的目标保持活动状态。所以如果我有:
publisher.SomeEvent += target.DoSomething;
then publisher
has a reference to target
but not the other way round.
然后publisher
有一个参考,target
但不是相反。
In your case, the publisher is going to be eligible for garbage collection (assuming there are no other references to it) so the fact that it's got a reference to the event handler targets is irrelevant.
在您的情况下,发布者将有资格进行垃圾收集(假设没有其他引用),因此它获得对事件处理程序目标的引用是无关紧要的。
The tricky case is when the publisher is long-lived but the subscribers don't want to be - in thatcase you need to unsubscribe the handlers. For example, suppose you have some data transfer service which lets you subscribe to asynchronous notifications about bandwidth changes, and the transfer service object is long-lived. If we do this:
棘手的情况是当发布者长期存在但订阅者不希望如此时 - 在这种情况下,您需要取消订阅处理程序。例如,假设您有一些数据传输服务,它允许您订阅有关带宽更改的异步通知,并且传输服务对象是长期存在的。如果我们这样做:
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;
(You'd actually want to use a finally block to make sure you don't leak the event handler.) If we didn't unsubscribe, then the BandwidthUI
would live at least as long as the transfer service.
(您实际上想要使用 finally 块来确保您不会泄漏事件处理程序。)如果我们没有取消订阅,那么BandwidthUI
它将至少与传输服务一样长。
Personally I rarely come across this - usually if I subscribe to an event, the target of that event lives at least as long as the publisher - a form will last as long as the button which is on it, for example. It's worth knowing about this potential issue, but I think some people worry about it when they needn't, because they don't know which way round the references go.
就我个人而言,我很少遇到这种情况 - 通常如果我订阅一个事件,该事件的目标至少与发布者一样长 - 例如,表单将与它上的按钮一样长。了解这个潜在问题是值得的,但我认为有些人在不需要的时候担心它,因为他们不知道引用的方向。
EDIT:This is to answer Jonathan Dickinson's comment. Firstly, look at the docs for Delegate.Equals(object)which clearly give the equality behaviour.
编辑:这是为了回答乔纳森迪金森的评论。首先,查看Delegate.Equals(object)的文档,它清楚地给出了相等行为。
Secondly, here's a short but complete program to show unsubscription working:
其次,这是一个简短但完整的程序来显示取消订阅的工作:
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();
}
}
Results:
结果:
Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers
(Tested on Mono and .NET 3.5SP1.)
(在 Mono 和 .NET 3.5SP1 上测试。)
Further edit:
进一步编辑:
This is to prove that an event publisher can be collected while there are still references to a subscriber.
这是为了证明可以在仍然引用订阅者的情况下收集事件发布者。
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);
}
}
Results (in .NET 3.5SP1; Mono appears to behave slightly oddly here. Will look into that some time):
结果(在 .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
回答by Eddie
In your case, you are fine. I originally read your question backwards, that a subscriberwas going out of scope, not the publisher. If the event publisher goes out of scope, then the referencesto the subscriber (not the subscriber itself, of course!) go with it and there is no need to explicitly remove them.
在你的情况下,你很好。我最初向后阅读您的问题,即订阅者超出范围,而不是发布者。如果事件发布者超出范围,则对订阅者的引用(当然不是订阅者本身!)随之而来,无需显式删除它们。
My original answer is below, about what happens if you create an event subscriberand let it go out of scope without unsubscribing. It does not apply to your question but I'll leave it in place for history.
我的原始答案如下,关于如果您创建一个事件订阅者并让它超出范围而不取消订阅会发生什么。它不适用于您的问题,但我会将其保留在历史中。
If the class is still registered via event handlers, then it is still reachable. It is still a live object. A GC following an event graph will find it connected. Yes, you will want to explicitly remove the event handlers.
如果该类仍然通过事件处理程序注册,那么它仍然可以访问。它仍然是一个活的对象。跟随事件图的 GC 会发现它是连接的。是的,您需要明确删除事件处理程序。
Just because the object is out of scope of its original allocation does not mean it is a candidate for GC. As long as a live reference remains, it is live.
仅仅因为对象超出其原始分配的范围并不意味着它是 GC 的候选对象。只要一个实时引用仍然存在,它就是实时的。