Soy un gran fan de usar IDisposable
para este tipo de cosas. De hecho, puede obtener excelentes resultados usando CompositeDisposable
para manejar todas sus necesidades de limpieza.
Aquí es lo que hago:
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)
{ /* ... */ }
}
Lo que esto le permite hacer es pegar todo tipo de código de limpieza en una colección que automáticamente se ejecuta una vez y sólo una vez cuando IDisposable.Dispose()
es llamado en su clase.
Esto es especialmente útil para los controladores de eventos, ya que le permite colocar un código adicional de controlador al lado para eliminar el código del controlador en su fuente y esto hace que la refactorización sea mucho más sencilla. Es muy fácil ver si realmente está eliminando controladores si el código está al lado del controlador de agregar.
Para que esto suceda, debe agregar dos clases a su código.
La primera es 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;
}
}
}
Y el segundo - que se utiliza en CompositeDisposable
- es 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 clase AnonymousDisposable
se utiliza para activar un Action
en un IDisposable
de modo que la acción se ejecuta cuando se dispone el AnonymousDisposable
.
Otra opción que ahora puede usar fácilmente es el uso de controladores de eventos anónimos en lugar de tener que definir métodos privados para manejar los eventos.
usted podría utilizar esto en el constructor en su lugar:
PropertyChangedEventHandler handler = (s, e) =>
{
// Use inline lambdas instead of private methods to handle events
};
model.PropertyChanged += handler;
_disposables.Add(() => model.PropertyChanged -= handler);
puede utilizar variables de nivel de método en el lamdbas, así que esta opción puede ayudar a mantener su módulo de conseguir todo desordenado.
Ahora, usted podría parar en esto, pero es posible que haya notado otra Add
sobrecarga en la clase CompositeDisposable
que ayuda a añadir suscripciones de eventos, así:
PropertyChangedEventHandler handler = (s, e) => { /* ... */ };
_disposables.Add(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
handler);
Esto hace todo el trabajo de suscribirse y darse de baja el controlador.
Usted puede incluso ir un paso más allá y hacerlo todo en una sola línea, así:
_disposables.Add<PropertyChangedEventHandler>(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
(s, e) =>
{
// ...
});
dulce, ¿eh?
Espero que esto ayude.
No soy educado en MVVM, pero mi primer pensamiento: * es el 'DetailViewModel: IDisposable' ... * ??? Si es así ... anular la suscripción en el método 'Dispose()'. – IAbstract
@IAbstract Creí que el recolector de basura usaba 'IDisposable' para los artículos que ya se estaban recogiendo; por ejemplo, para cerrar un archivo abierto o una colección de base de datos cuando no existen otras referencias al objeto. Sin embargo, esto evitará que el objeto se recopile en primer lugar, por lo que nunca se invocará 'Dispose()'. ¿Estoy malentendiendo 'IDisposable'? – mbmcavoy
Nunca he leído en ningún lado que no puedas leer. Además, creo que es el finalizador que ** en realidad ** desreferencia el objeto, o lanzamientos para GC ... ??? Creo que ... – IAbstract