如何在我处理完视图和视图模型但没有处理完模型时移除事件处理程序?

23 浏览
0 Comments

如何在我处理完视图和视图模型但没有处理完模型时移除事件处理程序?

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

因此,可以为给定的模型对象有多个视图模型,并且它们需要从其他位置更新更改(我在我的模型上使用了INotifyPropertyChanged)。当我使用ViewModels完成任务时,我想摆脱它们,也就是在关闭详细信息窗口时。

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

1)这是正确的吗?我该如何确定这些引用是否仍然存在?

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

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?
}

admin 更改状态以发布 2023年5月24日
0
0 Comments

一开始我认为这会是正确的方式:

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模式等其他方法……

0
0 Comments

我非常喜欢使用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(),它们就会自动地执行一次。

这对于事件处理程序特别有用,因为它允许你在源代码中将添加处理程序代码与移除处理程序代码放在一起,这使得重构变得更加简单。如果代码在添加处理程序旁边,那么很容易看出你是否真的正在移除处理程序。

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

第一个是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) =>
                {
                    // ...
                });

很棒,是吗?

希望这可以帮到你。

0