我如何删除事件处理程序时,我完成了一个视图和ViewModel,但不是模型

本文关键字:模型 一个 视图 ViewModel 何删除 删除 事件处理 程序 | 更新日期: 2023-09-27 18:08:32

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

因此,给定的模型对象可以有多个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?
}

我的理解是,事件处理程序将导致模型保留对ViewModel的引用,并防止它被垃圾收集。

1)正确吗?我如何知道这些引用是否仍然存在?

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

我如何删除事件处理程序时,我完成了一个视图和ViewModel,但不是模型

我非常喜欢使用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>, IDisposable
{
    private readonly List<IDisposable> _disposables;
    private bool _disposed;
    public CompositeDisposable()
    {
        _disposables = new List<IDisposable>();
    }
    public CompositeDisposable(IEnumerable<IDisposable> disposables)
    {
        if (disposables == null)
            { throw new ArgumentNullException("disposables"); }
        _disposables = new List<IDisposable>(disposables);
    }
    public CompositeDisposable(params IDisposable[] disposables)
    {
        if (disposables == null)
            { throw new ArgumentNullException("disposables"); }
        _disposables = new List<IDisposable>(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<TDelegate>(
            Action<TDelegate> add,
            Action<TDelegate> 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<IDisposable> GetEnumerator()
    {
        lock (_disposables)
        {
            return _disposables.ToArray().AsEnumerable().GetEnumerator();
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    public bool IsDisposed
    {
        get
        {
            return _disposed;
        }
    }
}

第二个是AnonymousDisposable,用于CompositeDisposable

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);

你可以在lambba中使用方法级变量,所以这个选项可以帮助你的模块保持混乱。

现在,您可以停在这里,但您可能已经注意到CompositeDisposable类中的另一个Add重载,它有助于添加事件订阅,如下所示:
        PropertyChangedEventHandler handler = (s, e) => { /* ... */ };
        _disposables.Add(
                    h => model.PropertyChanged += h,
                    h => model.PropertyChanged -= h,
                    handler);

完成从处理程序订阅和取消订阅的全部工作。

你甚至可以更进一步,在一行中完成,像这样:

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

甜,嗯?

一开始我以为会是这样:

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模式…

您可能想要考虑使用弱事件模式。我相信微软引入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, "");
    }
}