C# ViewModel是否必须取消订阅ViewModel中实例化的对象的事件?
我将WPF与MVVM一起使用。我有一个C# ViewModel是否必须取消订阅ViewModel中实例化的对象的事件?,c#,wpf,events,mvvm,C#,Wpf,Events,Mvvm,我将WPF与MVVM一起使用。我有一个ViewModel,它将对象MyService实例化为属性。ViewModel订阅MyService的事件。MyService属性绑定到视图中的某些元素 当不再使用ViewModel时,MyService是否会因为事件订阅而使我的ViewModel保持活动状态并防止垃圾收集(GC)?如果是,是否有简单的方法解决此问题?我应该在哪里取消订阅MyService?(但我无法控制调用我的视图/视图模型的用户) 通常,您应该始终取消订阅事件,最好是在事件处理程序中 p
ViewModel
,它将对象MyService
实例化为属性。ViewModel
订阅MyService
的事件。MyService
属性绑定到视图中的某些元素
当不再使用ViewModel
时,MyService
是否会因为事件订阅而使我的ViewModel
保持活动状态并防止垃圾收集(GC)?如果是,是否有简单的方法解决此问题?我应该在哪里取消订阅MyService
?(但我无法控制调用我的视图/视图模型的用户)
通常,您应该始终取消订阅事件,最好是在事件处理程序中
public void DownloadFile()
{
this.ServiceClient.DownloadCompleted += OnDownloadCompleted;
}
public void OnDownloadCompleted(object sender, EventArgs e)
{
this.ServiceClient.DownloadCompleted -= OnDownloadCompleted;
// Do something
}
在不知道事件源的生存期的情况下,使用弱事件模式或IDisposable
模式(但弱事件模式应该是首选)
要实现弱事件模式,您可以尝试使用现有的WeakEventManager
实现(例如PropertyChangedEventManager
)。或者,如果不存在,则可以使用通用的WeakEventManager
。由于此类使用反射来解析和订阅事件委托,因此建议扩展抽象类WeakEventManager
,以创建自定义类型。
看
public-MyService-MyService{get;set;}=new-MyService();
公共视图模型()
{
//MyService.MyEvent+=OnMyEvent;
WeakEventManager.AddHandler(
这是我的服务,
名称(MyService.MyEvent),
OnMyEvent);
}
如果可以避免取消订阅事件源或忽略弱事件模式,则取决于事件源的生存期
为了执行事件处理程序,事件源必须“知道”侦听器才能访问回调(或者更严格地说是为侦听器实例分配的内存空间)。因此,委托保留对实例的强引用,该引用存储在delegate.Target
属性中
如果事件源MyService
的寿命长于侦听器ViewModel
,则无法对侦听器进行垃圾收集,直到事件源本身被垃圾收集或强引用被删除(例如,通过取消订阅或将事件委托设置为null
)
这种情况是可能的,例如,当事件源是一个聚合实例时,该实例被允许在类的作用域之外活动或被引用,例如,通过公共属性或作为方法的返回值,或者事件源被定义为静态
在您的代码中,MyService
(事件源)定义为public
。这意味着ViewModel
(事件侦听器)无法控制此实例的生存期。
如果ViewModel
范围之外的某个生命周期长于ViewModel
的实例获得了对该public
属性值的引用,MyService
(因此事件侦听器ViewModel
)将保持活动状态,即使ViewModel
将属性MyService
设置为null
如果属性MyService
将是private
,并且您永远不会将此属性的引用返回给public
方法的调用方,那么您应该是安全的,因为MyService
的生存期现在与ViewModel
的生存期耦合。销毁ViewModel
也将销毁MyService
换句话说,您必须保证事件源的生存期与事件侦听器的生存期耦合(或更短),或者它们之间“无”耦合(弱事件模式,取消订阅)
您最好始终遵循订阅/取消订阅或WeakEventManager
的模式。这样,您就不必担心对象的生命周期,以防止内存泄漏
绑定将使用弱引用来订阅事件,因此,如果您正确地处置了VM,则不会出现问题。话虽如此,VM可能在GC的Gen2中,然后它将在那里停留一段时间,通常为10分钟。如果您有内存泄漏,那么我建议使用适当的工具(我使用的是ANTS free版本),并找到包含对您的服务的引用的内容。可能是其他地方的另一个实例?@XAMlMAXBindng
没有使用弱引用。它使用了对Binding.Source
的强烈引用。事实上,如果绑定到未实现INotifyPropertyChanged
的源,绑定引擎将创建一个静态字段来引用该源。由于静态内存永远不会被垃圾回收,因此会导致内存泄漏。这就是为什么绑定源“必须”实现INotyfyPropertyChanged
甚至更好(就性能而言,`implementdependencProperty
@XAMlMAX,但是Binding
正在使用弱事件模式,以便使用属性更改管理器
侦听属性更改的事件。感谢您提供的详细答案。我不确定当ViewModel拥有我的服务。但这很有意义,因为ViewModel和MyService都没有引用它们的根对象,我想,因此可以进行垃圾收集。剩下的问题是绑定。我需要检查在这种情况下是否实现了INPC,以确保绑定是通过弱引用进行的。我曾考虑过使用弱事件模式,但没有以前从未做过。也许我尝试一下,只是为了保存。如果MyService
是数据绑定的源,您必须实现INotifyPropertyChanged
。绑定当然会
public void DownloadFile()
{
this.ServiceClient.DownloadCompleted += OnDownloadCompleted;
}
public void OnDownloadCompleted(object sender, EventArgs e)
{
this.ServiceClient.DownloadCompleted -= OnDownloadCompleted;
// Do something
}
public MyService MyService { get; set; } = new MyService();
public ViewModel()
{
// MyService.MyEvent += OnMyEvent;
WeakEventManager<MyService, EventArgs>.AddHandler(
this.MyService,
nameof(MyService.MyEvent),
OnMyEvent);
}