Sono un grande fan dell'uso di IDisposable
per questo genere di cose. In effetti, puoi ottenere risultati eccellenti usando lo CompositeDisposable
per gestire tutte le tue esigenze di pulizia.
Ecco quello che faccio:
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)
{ /* ... */ }
}
Ciò permette di fare è bastone ogni sorta di codice di clean-up in una collezione che automaticamente viene eseguito una volta e solo una volta quando IDisposable.Dispose()
viene chiamato sulla tua classe.
Questo è particolarmente utile per i gestori di eventi in quanto consente di posizionare il codice del gestore di aggiunta accanto al codice del gestore di rimozione nella fonte e questo rende molto più semplice il refactoring. È molto semplice vedere se si stanno effettivamente rimuovendo i gestori se il codice si trova accanto al gestore aggiuntivo.
Per rendere questo accada è necessario aggiungere due classi al codice.
Il primo è 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;
}
}
}
E il secondo - che è utilizzato in 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();
}
}
}
La classe AnonymousDisposable
è usato per trasformare un Action
in un IDisposable
modo che l'azione viene eseguita quando il AnonymousDisposable
è disposto.
Un'ulteriore opzione che ora è possibile utilizzare facilmente è l'utilizzo di gestori di eventi anonimi piuttosto che la necessità di definire metodi privati per gestire gli eventi.
si potrebbe usare questo nel costruttore, invece:
PropertyChangedEventHandler handler = (s, e) =>
{
// Use inline lambdas instead of private methods to handle events
};
model.PropertyChanged += handler;
_disposables.Add(() => model.PropertyChanged -= handler);
È possibile utilizzare variabili a livello di metodo nella lamdbas, quindi questa opzione può aiutare a mantenere il modulo ottenere tutto disordinato.
Ora, si potrebbe fermarsi a questo, ma potreste aver notato un altro Add
sovraccarico nella classe CompositeDisposable
che aiuta l'aggiunta di sottoscrizioni di eventi, in questo modo:
PropertyChangedEventHandler handler = (s, e) => { /* ... */ };
_disposables.Add(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
handler);
Questo fa l'intera opera di Iscrizione e annullamento da il conduttore.
Si può anche fare un passo avanti e fare tutto in una sola riga, in questo modo:
_disposables.Add<PropertyChangedEventHandler>(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
(s, e) =>
{
// ...
});
dolce, eh?
Spero che questo aiuti.
Non sono ben informato su MVVM, ma il mio primo pensiero: * è il 'DetailViewModel: IDisposable' ... * ??? In tal caso ... annulla l'iscrizione nel metodo 'Dispose()'. – IAbstract
@IAbstract Ho pensato che 'IDisposable' è stato usato dal garbage collector per gli oggetti che sono già stati raccolti; ad esempio per chiudere un file aperto o una raccolta di database quando non esistono altri riferimenti all'oggetto. Tuttavia, questo bloccherà l'oggetto da essere raccolto in primo luogo, quindi 'Dispose()' non sarà mai chiamato. Sto fraintendendo 'IDisposable'? – mbmcavoy
Non ho mai letto da nessuna parte che non potresti. Inoltre, penso che sia il finalizzatore che ** in realtà ** dereferenzia l'oggetto, o rilascia per GC ... ??? Penso ... – IAbstract