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

C# 是否有必要在C中显式删除事件处理程序#,c#,garbage-collection,event-handling,C#,Garbage Collection,Event Handling,我有一门课提供一些活动。该类是全局声明的,但不是在该全局声明上实例化的——它是根据需要在需要它的方法中实例化的 每次在方法中需要该类时,都会实例化该类并注册事件处理程序。是否有必要在方法超出范围之前显式删除事件处理程序 当方法超出范围时,类的实例也会超出范围。将事件处理程序注册到超出范围的实例中是否意味着内存占用?(我想知道事件处理程序是否会阻止GC将类实例视为不再被引用。)在您的情况下,您很好。我最初是逆向阅读你的问题的,订阅者超出了范围,而不是出版商。如果事件发布服务器超出范围,那么对订阅服

我有一门课提供一些活动。该类是全局声明的,但不是在该全局声明上实例化的——它是根据需要在需要它的方法中实例化的

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


当方法超出范围时,类的实例也会超出范围。将事件处理程序注册到超出范围的实例中是否意味着内存占用?(我想知道事件处理程序是否会阻止GC将类实例视为不再被引用。)

在您的情况下,您很好。我最初是逆向阅读你的问题的,订阅者超出了范围,而不是出版商。如果事件发布服务器超出范围,那么对订阅服务器(当然不是订阅服务器本身!)的引用也会随之发生,而无需显式删除它们

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

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


仅仅因为对象超出了其原始分配的范围,并不意味着它是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块来确保不会泄漏事件处理程序。)如果我们不取消订阅,那么
带宽ui
将至少与传输服务一样长

就我个人而言,我很少遇到这种情况——通常是如果我订阅了一个活动,那么该活动的目标至少与发布者存在的时间一样长——例如,表单的持续时间与它上面的按钮的持续时间一样长。了解这个潜在的问题是值得的,但我认为有些人在不需要的时候会担心它,因为他们不知道引用的方向

编辑:这是为了回答乔纳森·迪金森的评论。首先,看看那些明确给出了平等行为的文件

其次,这里有一个简短但完整的程序来显示取消订阅的效果:

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在这里的行为似乎有点奇怪。过一段时间我们将对此进行研究):


我认为这里不需要取消订阅-GC看到的是来自事件发布者的引用,而不是它,我们关心的是发布者。@Jon Skeet:你说得对。我把问题倒读了一遍。我修正了我的答案以反映现实。我同意这一点,但如果可能的话,你能简要阐述一下或最好是举一个例子来说明你所说的“但订阅者不想成为”的意思吗?乔恩:非常感谢,这并不常见,但正如你所说,我看到人们不必要地担心这一点。-=不起作用。-=将产生一个新的委托,委托不使用目标方法检查相等性,而是对委托执行object.ReferenceEquals()。新委托在列表中不存在:它没有任何效果(并且不会奇怪地抛出错误)。@Jonathan:不,委托使用目标方法检查相等性。将在编辑中证明。我承认。我被匿名代表搞糊涂了。
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