2011-09-14 18 views
15

在我的應用程序中,我經常創建新的Views和ViewModels,但保留了相同的模型。例如,我可能會在我的主窗口中顯示一個項目列表的簡單視圖,並在另一個窗口中顯示任何特定項目的更多詳細信息。可以隨時打開和關閉細節窗口,也可以同時打開列表中不同項目的多個窗口。當我完成View和ViewModel而不是模型時,我該如何刪除事件處理程序

因此,對於給定的模型對象可以有多個ViewModel,並且需要更新來自其他位置的更改。 (我在我的模型上使用了INotifyPropertyChanged)。當我完成ViewModel時,即當細節窗口關閉時,我想擺脫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並取消訂閱事件?

+0

我沒有受過良好的MVVM教育,但我的第一個想法:*是'DetailViewModel:IDisposable' ... * ???如果是這樣...在Dispose()方法中取消訂閱。 – IAbstract

+0

@IAbstract我認爲'IDisposable'被垃圾收集器用於已經收集的物品;例如在沒有其他對該對象的引用存在時關閉打開的文件或數據庫集合。但是,這將阻止首先收集對象,所以'Dispose()'永遠不會被調用。我誤解'IDisposable'? – mbmcavoy

+0

我從未讀過任何你無法讀到的東西。此外,我認爲這是終結者,**實際**解除引用對象,或釋放GC ...?我認爲... – IAbstract

回答

8

起初我以爲這將是要走的路:

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

但後來我發現這個beautiful nugget。因此,至少有兩種可能的解決方案:(a)樣本實施IDisposable,和(b)針對IDisposable的論點。我會把辯論留給你。 ;)

您也可以考慮在其他中WeakEvent Pattern ...

+0

很好的參考。我真的很感謝@LBugnion自己回答了這個問題,因爲我自己使用MVVM-Light。 – mbmcavoy

+1

簡單的'IDisposable'實現工作! – mbmcavoy

1

你可能要考慮使用Weak Event Pattern。我相信微軟推出了WeakEventManagerIWeakEventListener來解決這個確切的垃圾收集問題。

9

我很喜歡使用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>, 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; 
     } 
    } 
} 

第二個 - 它在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用於打開一個ActionIDisposable使得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<PropertyChangedEventHandler>(
      h => model.PropertyChanged += h, 
      h => model.PropertyChanged -= h, 
      (s, e) => 
       { 
        // ... 
       }); 

甜,是吧?

我希望這會有所幫助。

+0

我需要一點時間來消化這一點 - 我的大腦瘋狂了!我仍然不明白什麼叫Dispose()。我是否需要確定何時完成ViewModel,並自己調用它? – mbmcavoy

+0

@mbmcavoy - 你需要調用'Dispose'。我的解決方案可以讓你在整個代碼中建立一套清理行爲,並且有一個簡單的方法來清理它。 – Enigmativity

+0

正是我所需要的。很酷,謝謝! – HolySamosa

相關問題