54

我有 3 个关于事件的问题:

  1. 我应该总是取消订阅已订阅的事件吗?
  2. 如果我不这样做会怎样?
  3. 在以下示例中,您将如何取消订阅已订阅的事件?

例如,我有以下代码:

Tor:目的:用于数据库属性更新

this.PropertyChanged += (o, e) =>
{
    switch (e.PropertyName)
    {
        case "FirstName": break;
        case "LastName": break;
    }
};

这: 目的:对于 GUI 绑定,将模型包装到视图模型中

ObservableCollection<Period> periods = _lpRepo.GetDailyLessonPlanner(data.DailyDate);
PeriodListViewModel = new ObservableCollection<PeriodViewModel>();

foreach (Period period in periods)
{
    PeriodViewModel periodViewModel = new PeriodViewModel(period,_lpRepo);
    foreach (DocumentListViewModel documentListViewModel in periodViewModel.DocumentViewModelList)
    {
        documentListViewModel.DeleteDocumentDelegate += new Action<List<Document>>(OnDeleteDocument);
        documentListViewModel.AddDocumentDelegate += new Action(OnAddDocument);
        documentListViewModel.OpenDocumentDelegate += new Action<int, string>(OnOpenDocument);
    }
    PeriodListViewModel.Add(periodViewModel);
}
4

5 回答 5

66

好吧,让我们先回答最后一个问题。您无法可靠地取消订阅您直接使用 lambda 表达式订阅的事件。您需要在委托中保留一个变量(因此您仍然可以使用 lambda 表达式),或者您需要使用方法组转换。

现在至于是否真的需要退订,就看事件生产者和事件消费者的关系了。如果事件生产者的生存时间应该比事件消费者长,你应该取消订阅 - 因为否则生产者将拥有对消费者的引用,使其存活的时间比它应该的更长。只要生产者产生事件处理程序,它也会一直被调用。

现在在许多情况下这不是问题 - 例如,在表单中,引发Click事件的按钮可能会与创建它的表单一样长,通常订阅处理程序......所以没有必要退订。这对于 GUI 来说是非常典型的。

同样,如果您WebClient仅出于单个异步请求的目的创建一个,订阅相关事件并启动异步请求,那么WebClient当请求完成时,它本身将有资格进行垃圾收集(假设您没有在其他地方保留引用)。

基本上,您应该始终考虑生产者和消费者之间的关系。如果生产者的寿命比您希望消费者的寿命长,或者在您不再对它感兴趣后它会继续引发事件,那么您应该取消订阅。

于 2010-11-13T13:54:07.293 回答
37

1)这取决于。通常这是一个好主意,但在某些典型情况下您不需要这样做。基本上,如果您确定订阅对象的寿命将超过事件源,您应该取消订阅,否则会产生不必要的引用。

但是,如果您的对象订阅了自己的事件,如下所示:

<Window Loaded="self_Loaded" ...>...</Window>

——那你就不必了。

2) 订阅事件会对订阅对象产生额外的引用。因此,如果您不取消订阅,您的对象可能会通过此引用保持活动状态,从而有效地造成内存泄漏。通过取消订阅,您将删除该引用。请注意,在自行订阅的情况下,不会出现问题。

3)你可以这样做:

this.PropertyChanged += PropertyChangedHandler;
...
this.PropertyChanged -= PropertyChangedHandler;

在哪里

void PropertyChangedHandler(object o, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "FirstName": break;
        case "LastName": break;
    }
}
于 2010-11-13T13:53:49.690 回答
7

当正在订阅的实例与正在订阅的实例具有相同的范围时,您不必取消订阅事件。

假设您是一个表单并且您正在订阅一个控件,这两者一起形成了一个组。但是,如果您有一个管理表单的中心类并且您已经订阅了Closed该表单的事件,那么这些不会一起形成一个组,并且您必须在表单关闭后取消订阅。

订阅事件会使订阅的实例创建对正在订阅的实例的引用。这可以防止垃圾收集。因此,当您有一个管理表单实例的中心类时,这会将所有表单保存在内存中。

WPF 是一个例外,因为它有一个弱事件模型,其中使用弱引用订阅事件并且它不会将表单保存在内存中。但是,当您不属于该表单时,最好还是取消订阅。

于 2010-11-13T13:55:39.070 回答
5

您可以查看 MSDN 上的这篇文章。引用:

要防止在引发事件时调用您的事件处理程序,只需取消订阅该事件即可。为了防止资源泄漏,在处理订阅者对象之前取消订阅事件很重要。在您取消订阅某个事件之前,发布对象中作为该事件基础的多播委托具有对封装订阅者事件处理程序的委托的引用。只要发布对象持有该引用,您的订阅者对象就不会被垃圾回收。

于 2010-11-13T13:51:19.770 回答
1

1.) 我应该总是取消订阅已订阅的事件吗?
通常是的。唯一的例外是您订阅的对象不再被引用并且很快就会被垃圾回收。

2.) 如果我不这样做会怎样?
您订阅的对象将持有对委托的引用,而委托又持有对其this指针的引用,因此您将获得内存泄漏。
或者,如果处理程序是 lamda,它将保留它绑定的任何局部变量,因此也不会被收集。

于 2010-11-13T13:53:58.080 回答