Я большой поклонник использования 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
используется для поворота 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, так что эта опция может помочь сохранить ваш модуль в беспорядке..
Теперь вы могли бы остановиться на этом, но вы могли заметить другую перегрузку Add
в классе CompositeDisposable
, которая помогает добавлять подписки на события, например:
PropertyChangedEventHandler handler = (s, e) => { /* ... */ };
_disposables.Add(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
handler);
Thisвыполняет всю работу по подписке и отпискеng от обработчика.
Вы можете даже сделать еще один шаг и сделать все это в одной строке, например так:
_disposables.Add<PropertyChangedEventHandler>(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
(s, e) =>
{
// ...
});
Сладко, да?
Надеюсьэто помогает.