如何在完成View和ViewModel时删除事件处理程序,而不是模型

在我的应用程序中,我经常创建新的视图和ViewModel,但持久化相同的模型。 例如,我可能会在主窗口中显示项目列表的简单视图,并有另一个窗口,其中包含任何特定项目的更多详细信息。 可以随时打开和关闭详细信息窗口,也可以同时打开列表中不同项目的多个窗口。

因此,给定模型对象可以有多个ViewModel,并且需要使用其他位置的更改进行更新。 (我在我的模型上使用了INotifyPropertyChanged 。)当我完成它时,我希望摆脱ViewModels,即,当详细信息窗口关闭时。

 public DetailViewModel(MyDetailModel detailModel) { // Retain the Detail Model this.model = detailModel; // Handle changes to the Model not coming from this ViewModel this.model.PropertyChanged += model_PropertyChanged; // Potential leak? } 

据我所知,事件处理程序将使Model保留对ViewModel的引用,并防止它被垃圾收集。

1)这是对的吗? 如何判断这些引用是否仍然存在?

2)我应该如何确定不再需要ViewModel并取消订阅事件?

起初我认为这将是要走的路:

 public class DetailViewModel : IDisposable { public DetailViewModel(MyDetailModel detailModel) { // Retain the Detail Model this.model = detailModel; // Handle changes to the Model not coming from this ViewModel this.model.PropertyChanged += model_PropertyChanged; // Potential leak? } public void Dispose() { this.model.PropertyChanged -= model_PropertyChanged; } } 

但后来我发现了这个美丽的金块 。 因此,至少有两种可能的解决方案:(a)实现IDisposable示例,以及(b)针对IDisposable参数。 我会把辩论留给你。 ;)

你也可以考虑WeakEvent模式 ……

我很喜欢使用IDisposable这类事情。 事实上,使用CompositeDisposable可以获得出色的结果,以满足您的所有清理需求。

这是我做的:

 public class DetailViewModel : IDisposable { private readonly CompositeDisposable _disposables = new CompositeDisposable(); public void Dispose() { _disposables.Dispose(); } private readonly MyDetailModel _model; public DetailViewModel(MyDetailModel model) { _model = model; _model.PropertyChanged += _model_PropertyChanged; Action removeHandler = () => _model.PropertyChanged -= _model_PropertyChanged; _disposables.Add(removeHandler); } private void _model_PropertyChanged( object sender, PropertyChangedEventArgs e) { /* ... */ } } 

这可以让你做的是将各种清理代码粘贴到一个集合中,该集合在你的类上调用IDisposable.Dispose()时自动运行一次且只运行一次。

这对于事件处理程序特别好,因为它允许您在源代码中删除处理程序代码旁边添加处理程序代码,这使得重构更加简单。 如果代码在add处理程序旁边,那么很容易看出你是否真的删除了处理程序。

要实现这一点,您需要在代码中添加两个类。

第一个是CompositeDisposable

 public sealed class CompositeDisposable : IEnumerable, IDisposable { private readonly List _disposables; private bool _disposed; public CompositeDisposable() { _disposables = new List(); } public CompositeDisposable(IEnumerable disposables) { if (disposables == null) { throw new ArgumentNullException("disposables"); } _disposables = new List(disposables); } public CompositeDisposable(params IDisposable[] disposables) { if (disposables == null) { throw new ArgumentNullException("disposables"); } _disposables = new List(disposables); } public void Add(IDisposable disposable) { if (disposable == null) { throw new ArgumentNullException("disposable"); } lock (_disposables) { if (_disposed) { disposable.Dispose(); } else { _disposables.Add(disposable); } } } public IDisposable Add(Action action) { if (action == null) { throw new ArgumentNullException("action"); } var disposable = new AnonymousDisposable(action); this.Add(disposable); return disposable; } public IDisposable Add( Action add, Action remove, TDelegate handler) { if (add == null) { throw new ArgumentNullException("add"); } if (remove == null) { throw new ArgumentNullException("remove"); } if (handler == null) { throw new ArgumentNullException("handler"); } add(handler); return this.Add(() => remove(handler)); } public void Clear() { lock (_disposables) { var disposables = _disposables.ToArray(); _disposables.Clear(); Array.ForEach(disposables, d => d.Dispose()); } } public void Dispose() { lock (_disposables) { if (!_disposed) { this.Clear(); } _disposed = true; } } public IEnumerator GetEnumerator() { lock (_disposables) { return _disposables.ToArray().AsEnumerable().GetEnumerator(); } } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public bool IsDisposed { get { return _disposed; } } } 

而第二个 – 在CompositeDisposable – 是AnonymousDisposable

 public sealed class AnonymousDisposable : IDisposable { private readonly Action _action; private int _disposed; public AnonymousDisposable(Action action) { _action = action; } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { _action(); } } } 

AnonymousDisposable类用于将Action转换为IDisposable以便在释放AnonymousDisposable时运行该操作。

您现在可以轻松使用的另一个选项是使用匿名事件处理程序,而不是需要定义私有方法来处理事件。

您可以在构造函数中使用它:

  PropertyChangedEventHandler handler = (s, e) => { // Use inline lambdas instead of private methods to handle events }; model.PropertyChanged += handler; _disposables.Add(() => model.PropertyChanged -= handler); 

您可以在lamdbas中使用方法级变量,因此该选项可以帮助您保持模块的混乱。

现在,您可以停止此操作,但您可能已经注意到CompositeDisposable类中的另一个Add重载有助于添加事件订阅,如下所示:

  PropertyChangedEventHandler handler = (s, e) => { /* ... */ }; _disposables.Add( h => model.PropertyChanged += h, h => model.PropertyChanged -= h, handler); 

这完成了从处理程序订阅和取消订阅的整个工作。

您甚至可以更进一步,在一行中完成所有操作,如下所示:

  _disposables.Add( h => model.PropertyChanged += h, h => model.PropertyChanged -= h, (s, e) => { // ... }); 

甜,对吧?

我希望这有帮助。

您可能需要考虑使用弱事件模式 。 我相信Microsoft引入了WeakEventManagerIWeakEventListener来解决这个确切的垃圾收集问题。

我正在关注IAbstract的答案,WPF直接通过PropertyChangedEventManager实现,更多可以在那里找到。

最终代码可能如下所示:

 public class DetailViewModel : IDisposable { public DetailViewModel(MyDetailModel detailModel) { // Retain the Detail Model this.model = detailModel; // Handle changes to the Model not coming from this ViewModel if(model != null) PropertyChangedEventManager.AddHandler(model, model_PropertyChanged, ""); } public void Dispose() { if(model != null) PropertyChangedEventManager.RemoveHandler(model, model_PropertyChanged, ""); } }