Memory leak using PropertyDescriptor AddValueChanged

By Mirek on (tags: memory leak, WPF, categories: code)

Recently I found out that my attached property, which I am using to modify the dependency property of one of my controls, causes a memory leak.

Finally it turned out that it is not the attached property, but the titled method of PropertyDescriptor I have used in my code. The AddValueChanged method allows to monitor changes on any dependency property of a component. In my case I wanted to be notified when ItemsSource property changes when I attach a ShowLimit property to any kind of ItemsControl based component.

   1: public static readonly DependencyProperty ShowLimit =
   2:             DependencyProperty.RegisterAttached("ShowLimit", typeof(int),
   3:             typeof(Attach), new PropertyMetadata(0, ShowLimitChanged));
   5: [...]
   7: private static void ShowLimitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   8: {
   9:     ItemsControl control = (ItemsControl)d;
  11:     if (((int)(e.NewValue)) > 0)
  12:         TypeDescriptor.GetProperties(control)["ItemsSource"].AddValueChanged(control, ItemsSourceChangedHandler);
  13:     else
  14:         TypeDescriptor.GetProperties(control)["ItemsSource"].RemoveValueChanged(control, ItemsSourceChangedHandler);
  15: }
  18: private static EventHandler ItemsSourceChangedHandler = new EventHandler(ItemsSource_Changed);
  19: private static void ItemsSource_Changed(object sender, EventArgs e)
  20: {
  21:     [...]
  22: }

As well described by Andrew Smith, AddValueChange method creates a strong reference to the monitored component and holds it in a hashtable internally. It basically causes that monitored component cannot be garbage collected.
Andrew’s solution is good, however seems to be a little bit to heavy for this case. To prevent a memory leak the all we need is to remember to remove the event handler when the component is no longer in use. We can do it when the control I calling a Unloaded event.

   1: private static void ShowLimitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   2: {
   3:     ItemsControl control = (ItemsControl)d;
   5:     if (((int)(e.NewValue)) > 0)
   6:         TypeDescriptor.GetProperties(control)["ItemsSource"].AddValueChanged(control, ItemsSourceChangedHandler);
   7:     else
   8:         TypeDescriptor.GetProperties(control)["ItemsSource"].RemoveValueChanged(control, ItemsSourceChangedHandler);
  10:     control.Unloaded += (s, args) =>
  11:     {
  12:         TypeDescriptor.GetProperties(s)["ItemsSource"].RemoveValueChanged(s, ItemsSourceChangedHandler);
  13:     };
  14: }

In lines 10 to 13 we register a anonymous event handler which is invoked when the control is unloaded. We then remove the ItemsSourceChangeHandler causing the control to be removed from the internal PropertyDescriptor registry. This solution is simple, and works as expected.