我应该取消订阅活动吗?

Eli*_*eth 44 c# events unsubscribe subscribe

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

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

我有这样的代码:

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

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

这个:目的:用于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);
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 55

好吧,让我们先回答最后一个问题.您无法可靠地取消订阅您直接使用lambda表达式订阅的事件.你要么需要与委托(所以你仍然可以使用lambda表达式)围绕保持一个变量或者你需要使用方法组转换代替.

现在至于你是否真的需要取消订阅,这取决于事件生产者和事件消费者之间的关系.如果事件生成者的活动时间比事件消费者长,则应取消订阅 - 因为否则生产者将引用消费者,使其保持活动的时间超过应有的时间.只要生产者生成它,事件处理程序也将继续被调用.

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

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

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

  • @bazsisz:事件生成器是引发事件的东西,例如一个按钮说“我被点击了”。事件使用者是监听(或“订阅”事件)的事物,它想要对发生的事件做出反应。 (2认同)

Vla*_*lad 30

1)这取决于.通常这是一个好主意,但有一些典型的情况你不需要.基本上,如果您确定订阅对象将比事件源更长,那么您应该取消订阅,否则会产生不必要的引用.

但是,如果您的对象正在订阅自己的事件,请执行以下操作:

<Window Loaded="self_Loaded" ...>...</Window>
Run Code Online (Sandbox Code Playgroud)

- 然后你不必.

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

3)你可以这样做:

this.PropertyChanged += PropertyChangedHandler;
...
this.PropertyChanged -= PropertyChangedHandler;
Run Code Online (Sandbox Code Playgroud)

哪里

void PropertyChangedHandler(object o, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "FirstName": break;
        case "LastName": break;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你的第二句似乎倒退了.由于源保持对订户的引用,因此如果订阅对象超过源,则不会有问题.源仍然可以被垃圾收集. (4认同)

Pie*_*kel 6

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

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

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

WPF 是一个例外,因为它具有弱事件模型,其中使用弱引用订阅事件,并且不会将表单保存在内存中。但是,当您不是表单的一部分时,取消订阅仍然是最佳做法。


Dar*_*rov 5

您可以在MSDN上查看此文章。引用:

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