代码之家  ›  专栏  ›  技术社区  ›  rp.

是否有必要在C中显式删除事件处理程序#

  •  112
  • rp.  · 技术社区  · 15 年前

    每次在方法中需要该类时,都会实例化该类并注册事件处理程序。是否有必要在方法超出范围之前显式删除事件处理程序?

    2 回复  |  直到 5 年前
        1
  •  184
  •   Jon Skeet    8 年前

    对你来说,一切都很好。这是一个 保持平衡的事件 目标 所有事件处理程序都处于活动状态。因此,如果我有:

    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;
    

    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和.NET3.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);
        }
    }
    

    结果(在.NET3.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
    
        2
  •  8
  •   Eddie    15 年前

    订阅人 出版商 . 如果事件发布服务器超出范围,则 对于订阅服务器(当然不是订阅服务器本身!)使用它,不需要显式删除它们。

    下面是我最初的答案,关于如果创建一个事件会发生什么 订阅人 让它在不取消订阅的情况下超出范围。这不适用于你的问题,但我将把它留在历史上。

    如果该类仍然是通过事件处理程序注册的,那么它仍然是可访问的。它仍然是一个活的物体。遵循事件图的GC将发现它已连接。是的,您需要显式删除事件处理程序。