我将 WPF 与 MVVM 结合使用。我有一个
ViewModel
将对象 MyService
实例化为属性。 ViewModel
订阅 MyService
的事件。 MyService
属性绑定到 View
中的某些元素。
当不再使用
ViewModel
时,由于事件订阅,MyService
会保持我的 ViewModel
处于活动状态并防止垃圾收集 (GC) 吗? 如果是,是否有简单的方法来解决此问题?我应该在哪里取消订阅MyService
? (尽管我无法控制调用我的 View/Viewmodel 的人)
public class ViewModel
{
public MyService MyService { get; set; } = new MyService();
public ViewModel()
{
MyService.MyEvent += OnMyEvent;
}
private void OnMyEvent(object sender, EventArgs e)
{
// do something
}
}
一般来说,您应该始终取消订阅事件,最好是在事件处理程序中取消订阅。
public void DownloadFile()
{
this.ServiceClient.DownloadCompleted += OnDownloadCompleted;
}
public void OnDownloadCompleted(object sender, EventArgs e)
{
this.ServiceClient.DownloadCompleted -= OnDownloadCompleted;
// Do something
}
在您不知道事件源生命周期的情况下,请使用弱事件模式或
IDisposable
模式(但弱事件模式应该是首选)。
要实现弱事件模式,您可以尝试使用现有的
WeakEventManager
实现(例如 PropertyChangedEventManager
)。或者,如果不存在,您可以使用通用的 WeakEventManager<T>
。由于此类使用反射来解析和订阅事件委托,因此建议扩展抽象类WeakEventManager
来创建自定义类型。public MyService MyService { get; set; } = new MyService();
public ViewModel()
{
// MyService.MyEvent += OnMyEvent;
WeakEventManager<MyService, EventArgs>.AddHandler(
this.MyService,
nameof(MyService.MyEvent),
OnMyEvent);
}
是否可以避免取消订阅事件源或忽略弱事件模式,取决于事件源的生命周期。
为了执行事件处理程序,事件源必须“知道”侦听器才能访问回调(或更技术地说,是为侦听器实例分配的内存空间)。因此,委托保留对实例的强引用,该引用存储在
Delegate.Target
属性中。
如果事件源
MyService
的寿命比侦听器ViewModel
长,则侦听器无法被垃圾回收,直到事件源本身被垃圾回收或强引用被删除(例如通过取消订阅或设置事件委托)到null
)。
这种情况是可能的,例如,当事件源是聚合实例时,允许在类范围之外存在或引用,例如通过公共属性或作为方法的返回值或定义事件源
static
.
在您的代码中
MyService
(事件源)已定义public
。这意味着 ViewModel
(事件监听器)无法控制此实例的生命周期。ViewModel
范围之外的某个生命周期比 ViewModel
更长的实例获得对此 public
属性值的引用,则 MyService
(因此事件侦听器 ViewModel
)将保持活动状态,即使if ViewModel
将属性 MyService
设置为null
。
如果属性
MyService
是 private
并且您永远不会将此属性的引用返回给 public
方法的调用者,那么您应该是安全的,因为 MyService
的生命周期现在与ViewModel
的生命周期。破坏ViewModel
也会破坏MyService
。您最好始终遵循订阅/取消订阅或
WeakEventManager
的模式。这样你就不必担心对象的生命周期来防止内存泄漏。
否则,您可以使用Lambda Function来管理事件,它会自动销毁。