In the past couple of weeks, I was taking part in a focused effort to find and fix memory-related issues in one of my client’s applications. In this post I want to write about a specific type of memory leak that I found more than once, which is related to Blend-behaviors.
As an example, consider the following behavior:
It is not important what the behavior is actually doing, only that in order for it to work, it needs to be notified when the Margin property of the AssociatedObject changes. It uses the DependencyPropertyDescriptor class to add an event handler to be called when the property’s value changes. The event handler is removed in the OnDetaching method, which is supposed to be called when the behavior is not used anymore.
At first glance, the code seems to be perfectly fine, but in fact, it hides a memory leak which is caused by the following two reasons:
- The AddValueChanged method adds a strong reference to the AssociatedObject in a static dictionary. This reference is removed only when the RemoveValueChanged method is called.
- The behavior’s OnDetaching method is never being called, which means that the RemoveValueChanged method is never being called as well.
While debugging the issue in WinDbg, You won’t see the behavior itself in the reference chain, because it is not the one that holds the reference to the AssociatedObject.
As you can see, the DependencyObjectPropertyDescriptor holds a reference to the AssociatedObject (a Button control in that case), which holds a reference to the view model (the Item class), probably via the DataContext property. The DependencyObjectPropertyDescriptor class itself is being referenced by a static dictionary. Because of that, the button, the view model, and the behavior, will never be garbage-collected.
Note that the usage of the DependencyPropertyDescriptor class is just one example of a memory leak that can be caused by a behavior. As a general rule, the behavior’s reference and the AssociatedObject’s reference should never be held by objects with longer lifetimes than the AssociatedObject itself (which is not the case with the static dictionary in the above example). You should never rely on the OnDetaching method to remove those references, because it is never being called.
To fix the above memory leak, you can use a wonderful solution that was posted online, in 2008, by Andrew Smith. It leverages the fact that the binding mechanism holds a WeakReference to the source object.
A second solution is to implement and use the WeakEventManager class (as described here), but in my experience, its purging semantics does not always call the StopListening method in the above scenario (Based on a quick code inspection, it seems that the WeakEventManager class calls the StopListening method for unreferenced handlers when a handler is added or removed, or when the event is raised, none of which happens in our scenario).
Another solution, which is not always possible, is to add a handler to direct events of the AssociatedObject. That way, the behavior’s reference will be held by the AssociatedObject itself, and not by the static dictionary. In the above example we cannot do it, because the FrameworkElement class does not have a MarginChanged event or something similar.
I hope that I’ve saved you some headache by pointing you in the right direction.