W ostatnich postach pisałem o słabych referencjach oraz podałem przykładowe implementacje obsługi zdarzeń. Niestety mimo generycznego rozwiązania, jednej wady nie udało się usunąć a mianowicie tzw. sacrifice object. Przed rozpoczęciem czytania tego wpisu zachęcam najpierw do zapoznania się z poprzednimi.
WeakEventManager to klasa WPF. Jak wspomniałem, często nie wiemy kiedy listener jest usuwany z pamięci w różnego rodzaju kontrolkach. Z tego względu to właśnie WPF dostarcza odpowiednią klasę. W wersji 4.5 WeakEventManager znacząco został uproszczony i naprawdę niewiele trzeba pisać własnego kodu. Przed pojawieniem się 4.5 również istniała możliwość wykorzystania WeakEventManager ale wiązało to się z implementacją interfejsu i pewnej klasy bazowej. Nie było to eleganckie ale do najczęściej wykorzystywanych typów zdarzeń (PropertyChanged, ButtonClick) istniały już zaimplementowane managery. Od wersji 4.5 wszystko jest po prostu generyczne.
Przykład:
public class SampleClass : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void RefreshProperty(string propName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } public class ViewModel { public ViewModel(SampleClass sampleClass) { WeakEventManager<SampleClass, PropertyChangedEventArgs>.AddHandler(sampleClass, "PropertyChanged", SampleClassPropertyChanged); } void SampleClassPropertyChanged(object sender, PropertyChangedEventArgs e) { // do something } }
Jak widać wystarczyło wywołać tylko jedną metodę:
WeakEventManager<SampleClass, PropertyChangedEventArgs>.AddHandler(sampleClass, "PropertyChanged", SampleClassPropertyChanged);
AddHandler to statyczna metoda. Pierwszy parametr to obiekt generujący zdarzenie, drugi to nazwa generowanego zdarzenia a trzeci to handler czyli metoda obsługująca te zdarzenie. To jest na prawdę wszystko co musimy napisać! W porównaniu do poprzednich wersji WPF, kod jest dużo prostszy. Dostaliśmy generyczne rozwiązanie, które nigdy nie pozostawia tzw. sacrifice objects (patrz poprzednie wpisy).
Przejdźmy do naszego standardowego testu:
SampleClass sampleClass = new SampleClass(); ViewModel viewModel = new ViewModel(sampleClass); sampleClass.RefreshProperty(null); viewModel = null; GC.Collect(); sampleClass.RefreshProperty(null);
Oczywiście SamplePropertyChanged zostanie wywołane tylko raz. Aby przekonać się, że bez WeakEventManger mielibyśmy memory leak wystarczy podmienić wywołanie AddHandler, zwykłą silną referencją:
public class ViewModel { public ViewModel(SampleClass sampleClass) { sampleClass.PropertyChanged += SampleClassPropertyChanged; } void SampleClassPropertyChanged(object sender, PropertyChangedEventArgs e) { // do something } }
Po tym zabiegu SampleClassPropertyChanged zostanie wywołane dwukrotnie.