在C#中有必要明确删除事件处理程序吗?

26 浏览
0 Comments

在C#中有必要明确删除事件处理程序吗?

我有一个类提供一些事件。这个类是全局声明但不是全局实例化的-它在需要它的方法中按需实例化。每次在方法中需要这个类时,都会实例化并注册事件处理程序。在方法退出作用域之前,是否需要显式删除事件处理程序?当方法退出作用域时,类的实例也会退出作用域。在这个正在退出作用域的实例保留事件处理程序是否有内存印象呢?(我想知道事件处理程序是否防止GC认为该类实例已不再被引用。)

admin 更改状态以发布 2023年5月22日
0
0 Comments

在你的情况下,一切都好。最初我误读了你的问题,认为订阅者正在超出作用域,而不是发布者。如果事件发布者超出作用域,那么对订阅者的引用(当然不是订阅者本身!)也随之消失,无需显式地将其删除。我的原始答案如下,讨论了如果创建事件订阅者后让其超出作用域而不取消订阅会发生什么。它不适用于你的问题,但我会留下它以供参考。如果仍然通过事件处理程序注册了该类,则它仍然是可访问的。它仍然是一个活动对象。在事件图中跟随的GC将找到它连接的位置。是的,你将需要显式地删除事件处理程序。仅仅因为对象超出了其原始分配的作用域并不意味着它是GC的一个候选对象。只要仍然存在实时引用,它就是活动的。

0
0 Comments

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

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

0