I’ve been meaning to write about this for a while since I’ve seen this approach mentioned lots of times on the newsgroups but have seen no mentions about the caveats. Just today I saw the same problem in some code from one of the disciples. The scenario is that you want to know when the value of a dependency property changes but you don’t have a one to one relationship with the object. For example, you have a list of items and you want to know when the IsSelected property of an item has changed.
The solution that I have seen given for this is to get to the PropertyDescriptor and use its AddValueChanged method to provide an EventHandler to receive a notification when the property has changed. Sometimes, the reply will mention/use DependencyPropertyDescriptor directly but its the same thing since that is just a derived PropertyDescriptor that provides additional information about the underlying DependencyProperty it represents. You can get to this property descriptor in a few ways but the most common are to get it from the TypeDescriptor.GetProperties method or using the DependencyPropertyDescriptor.FromProperty.
The issue with this approach is that it will root your object so it will never get collected by the GC. There have been plenty of discussions about how hooking events (particularly static events) can root your object so I won’t go into great detail there. While it does not seem that you are hooking a static event in this case, in essence you are. When you add a handler to a property descriptor, that property descriptor stores the delegate in a hashtable keyed by the object whose property you are hooking. A delegate/handler is basically a pointer to a method on an object (or no object if its for a static method) so that means the property descriptor has a reference to your object as well as the object whose value you are watching (since that is the key into the hashtable). The property descriptors themselves are cached statically so the hashtable is kept around and therefore your object and the one you are watching are as well.
I personally like to use the Scitech memory profiler (or you can use the Microsoft CLR Profiler) when debugging memory leak issues but you can see the issue manifest itself pretty easily in this case. First we’ll do a benchmark to make sure that we can check whether the object was collected.
If you run this code and check isAlive, you will see that it returns false indicating that the ListBoxItem was not referenced and was able to be collected. Now let’s try using the AddValueChanged method.
This time isAlive returns true because the property descriptor is maintaining a reference to the ListBoxItem. Now, let’s try an alternative approach that involves creating a helper class to listen for the property change.
In this case isAlive is false indicating that the object can be collected even though we’re still maintaining an explicit reference to the notifier. The implementation for the class – PropertyChangeNotifier – is listed below. The class is basically a simple DependencyObject that exposes 2 properties – Value returns the value of the property of the object that it is watching and PropertySource returns the object whose property it is watching. The constructor for the object takes the object whose property is to be watched for changes and the property that should be watched. This class takes advantage of the fact that bindings use weak references to manage associations so the class will not root the object who property changes it is watching. It also uses a WeakReference to maintain a reference to the object whose property it is watching without rooting that object. In this way, you can maintain a collection of these objects so that you can unhook the property change later without worrying about that collection rooting the object whose values you are watching.
Another possible approach would be to implement your own derived WeakEventManager but I’ll leave that as an exercise for the reader. For those that choose to go this route, there is such a class – except its internal – in the PresentationFramework assembly.
April 9, 2008 at 5:55 am |
Andrew,
Very nice! Thank you for sharing this solution.
Cheers,
Karl
April 9, 2008 at 12:22 pm |
Nice solution, Andrew. I like how you use Binding’s internal weakref in this case. Very cool!
One question for you…does the memory leak still exist if you call RemoveValueChanged on the property descriptor? Does that remove the entry from the internal hashtable?
Thanks,
Josh
April 9, 2008 at 12:44 pm |
No, it won’t leak memory if you remove the handler. When the last handler is removed, the item is removed from the hashtable. But if you’re relying on say an item being removed from the collection before you remove the handler, that may not happen – e.g. if you just close the form while the list is populated. In that case, you might be able to also unhook within the Unloaded event but then you have to know to re-hook again should your element get reloaded (e.g. if the element is in a tab control and you switch to another tab and back).
April 9, 2008 at 12:49 pm |
Thanks. That makes sense.
August 26, 2008 at 1:21 pm |
Hi Andrew,
Thanks for this great article! It really helped a lot with cleaning up the code I am working on currently.
I have been using WeakEventManager class with some other events but I have not been able to come up with a way to use it with DependencyPropertyDescriptor.
The reason being, the way you attach the event in weakeventmanager is
source.<> = DeliverEvent;
I am not able to fit DependencyPropertyDescripter into that structure.
Please let me know if you have any suggestions.
August 26, 2008 at 1:21 pm |
Correction to my above comment
The reason being, the way you attach the event in weakeventmanager is
source.Event += DeliverEvent;
August 26, 2008 at 2:00 pm |
Thanks. I’m glad it helped.
If you look at the internal ValueChangedEventManager class in the PresentationFramework assembly (which is what MS is using for this) you can see that they are using a custom class (ValueChangedRecord) to add a handler for the property changed of the PD and that class has a reference back to the WeakEventManager (and the listenerlist) and uses the manager’s DeliverEventToList method instead of the DeliverEvent method.
December 3, 2008 at 3:57 pm |
Hey Andrew, I had been looking all day for a replacement for WPF’s DependencyPropertyDescriptor and I came across this article. Unfortunately it looks like they removed the Bindable() attribute from the final Silverlight release – do you have an update to this code that works in the final bits?
Thanks,
Bob
February 23, 2009 at 10:15 am |
If you change this line
if (null != notifier.ValueChanged)
notifier.ValueChanged(notifier, EventArgs.Empty);
to this, you will get the correct sender in the event
if (null != notifier.ValueChanged)
notifier.ValueChanged(notifier.PropertySource, EventArgs.Empty);
February 23, 2009 at 10:18 am |
If you are just replacing the AddValueChanged to use this, remember you will need a reference to the notifier, the previous way (DependencyPropertyDescriptor) did as it has an internal hash map. maybe save someone a few minutes