Answers:
就您而言,一切都很好。它是发布事件的对象,使事件处理程序的目标保持活动状态。所以,如果我有:
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的候选对象。只要有实时参考,就可以保持实时。