WeakEventManager w WPF 4.5

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.

Leave a Reply

Your email address will not be published.