Jestem wielkim fanem używania IDisposable
dla tego rodzaju rzeczy. W rzeczywistości można uzyskać doskonałe wyniki, używając urządzenia CompositeDisposable
do obsługi wszystkich potrzeb związanych z czyszczeniem.
Oto, co zrobić:
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)
{ /* ... */ }
}
Co to pozwala zrobić to trzymać wszystkie rodzaje oczyszczaniem kodu do kolekcji, która automatycznie pobiera uruchomić tylko raz, gdy IDisposable.Dispose()
jest wywoływana na klasy.
Jest to szczególnie użyteczne w przypadku procedur obsługi zdarzeń, ponieważ umożliwia umieszczenie kodu obsługi dodatku obok kodu obsługi w źródle, co znacznie upraszcza refaktoryzację. Bardzo łatwo jest sprawdzić, czy faktycznie usuwasz programy obsługi, jeśli kod znajduje się obok procedury dodawania.
Aby to się stało, musisz dodać dwie klasy do swojego kodu.
Pierwszym jest 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;
}
}
}
a drugi - który jest stosowany w CompositeDisposable
- jest 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();
}
}
}
Klasa AnonymousDisposable
służy do włączania się w IDisposable
Action
w taki sposób, że działanie jest uruchamiany, gdy AnonymousDisposable
jest umieszczony.
Inną opcją, z której można teraz łatwo korzystać, jest korzystanie z anonimowych procedur obsługi zdarzeń, a nie konieczność definiowania prywatnych metod obsługi zdarzeń.
Można to wykorzystać w konstruktorze Zamiast:
PropertyChangedEventHandler handler = (s, e) =>
{
// Use inline lambdas instead of private methods to handle events
};
model.PropertyChanged += handler;
_disposables.Add(() => model.PropertyChanged -= handler);
Można używać zmiennych metoda szczebla w lamdbas, więc ta opcja może pomóc zachować swój moduł uzyskanie wszystkich bałagan.
Teraz można zatrzymać się na tym, ale można zauważyć inny Add
przeciążenie w klasie CompositeDisposable
że pomaga dodanie subskrypcji zdarzeń, tak jak poniżej:
PropertyChangedEventHandler handler = (s, e) => { /* ... */ };
_disposables.Add(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
handler);
To czyni całą pracę subskrybowania i wypisywania z przewodnik.
Można nawet pójść o krok dalej i zrobić to wszystko w jednej linii, tak:
_disposables.Add<PropertyChangedEventHandler>(
h => model.PropertyChanged += h,
h => model.PropertyChanged -= h,
(s, e) =>
{
// ...
});
słodki, hę?
Mam nadzieję, że to pomoże.
Nie jestem dobrze wykształcony w MVVM, ale moja pierwsza myśl: * to 'DetailViewModel: IDisposable' ... * ??? Jeśli tak ... zrezygnuj z subskrypcji w metodzie "Dispose()". – IAbstract
@Abstract Myślałem, że 'IDisposable' został użyty przez garbage collector dla przedmiotów, które już są zbierane; na przykład, aby zamknąć zbiór otwartych plików lub bazy danych, gdy nie istnieją żadne inne odwołania do obiektu. Jednak najpierw zablokuje to zbieranie obiektu, więc 'Dispose()' nigdy nie zostanie wywołane. Czy nie rozumiem "IDisposable"? – mbmcavoy
Nigdy nie czytałem nigdzie, czego nie można. Sądzę też, że to finalizator ** faktycznie ** usuwa obiekt, lub wydania dla GC ... ??? Myślę, że ... – IAbstract