Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/338.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# ViewModel是否必须取消订阅ViewModel中实例化的对象的事件?_C#_Wpf_Events_Mvvm - Fatal编程技术网

C# ViewModel是否必须取消订阅ViewModel中实例化的对象的事件?

C# ViewModel是否必须取消订阅ViewModel中实例化的对象的事件?,c#,wpf,events,mvvm,C#,Wpf,Events,Mvvm,我将WPF与MVVM一起使用。我有一个ViewModel,它将对象MyService实例化为属性。ViewModel订阅MyService的事件。MyService属性绑定到视图中的某些元素 当不再使用ViewModel时,MyService是否会因为事件订阅而使我的ViewModel保持活动状态并防止垃圾收集(GC)?如果是,是否有简单的方法解决此问题?我应该在哪里取消订阅MyService?(但我无法控制调用我的视图/视图模型的用户) 通常,您应该始终取消订阅事件,最好是在事件处理程序中 p

我将WPF与MVVM一起使用。我有一个
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版本),并找到包含对您的服务的引用的内容。可能是其他地方的另一个实例?@XAMlMAX
Bindng
没有使用弱引用。它使用了对
Binding.Source
的强烈引用。事实上,如果绑定到未实现
INotifyPropertyChanged
的源,绑定引擎将创建一个静态字段来引用该源。由于静态内存永远不会被垃圾回收,因此会导致内存泄漏。这就是为什么绑定源“必须”实现
INotyfyPropertyChanged
甚至更好(就性能而言,`implement
dependencProperty
@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);
}