PropertyDescriptor AddValueChanged Alternative

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.

ListBoxItem i = new ListBoxItem();
WeakReference wr = new WeakReference(i);
i = null;
GC.Collect();
bool isAlive = wr.IsAlive;

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.

ListBoxItem i = new ListBoxItem();
WeakReference wr = new WeakReference(i);
PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(ListBoxItem))["IsSelected"];
// the following yields the same pd
//PropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ListBoxItem.IsSelectedProperty, typeof(ListBoxItem));
prop.AddValueChanged(i, new EventHandler(this.OnValueChanged));
i = null;
GC.Collect();
bool isAlive = wr.IsAlive;

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.

ListBoxItem i = new ListBoxItem();
WeakReference wr = new WeakReference(i);
PropertyChangeNotifier notifier = new PropertyChangeNotifier(i, “IsSelected”);
notifier.ValueChanged += new EventHandler(OnValueChanged);
i = null;
GC.Collect();
bool isAlive = wr.IsAlive;

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.

public sealed class PropertyChangeNotifier :
DependencyObject,
IDisposable
{
#region Member Variables
private WeakReference _propertySource;
#endregion // Member Variables
 
#region Constructor
public PropertyChangeNotifier(DependencyObject propertySource, string path)
: this(propertySource, new PropertyPath(path))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, DependencyProperty property)
: this(propertySource, new PropertyPath(property))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, PropertyPath property)
{
if (null == propertySource)
throw new ArgumentNullException(“propertySource”);
if (null == property)
throw new ArgumentNullException(“property”);
this._propertySource = new WeakReference(propertySource);
Binding binding = new Binding();
binding.Path = property;
binding.Mode = BindingMode.OneWay;
binding.Source = propertySource;
BindingOperations.SetBinding(this, ValueProperty, binding);
}
#endregion // Constructor
 
#region PropertySource
public DependencyObject PropertySource
{
get 
{
try
{
// note, it is possible that accessing the target property
// will result in an exception so i’ve wrapped this check
// in a try catch
return this._propertySource.IsAlive
? this._propertySource.Target as DependencyObject
: null;
}
catch
{
return null;
}
}
}
#endregion // PropertySource
 
#region Value
/// <summary>
/// Identifies the <see cref=”Value”/> dependency property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(“Value”,
typeof(object), typeof(PropertyChangeNotifier), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged)));
 
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PropertyChangeNotifier notifier = (PropertyChangeNotifier)d;
if (null != notifier.ValueChanged)
notifier.ValueChanged(notifier, EventArgs.Empty);
}
 
/// <summary>
/// Returns/sets the value of the property
/// </summary>
/// <seealso cref=”ValueProperty”/>
[Description("Returns/sets the value of the property")]
[Category("Behavior")]
[Bindable(true)]
public object Value
{
get
{
return (object)this.GetValue(PropertyChangeNotifier.ValueProperty);
}
set
{
this.SetValue(PropertyChangeNotifier.ValueProperty, value);
}
}
#endregion //Value
 
#region Events
public event EventHandler ValueChanged;
#endregion // Events
 
#region IDisposable Members
public void Dispose()
{
BindingOperations.ClearBinding(this, ValueProperty);
}
#endregion
}

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.

About these ads

34 Responses to “PropertyDescriptor AddValueChanged Alternative”

  1. Karl Shifflett Says:

    Andrew,

    Very nice! Thank you for sharing this solution.

    Cheers,

    Karl

  2. Josh Smith Says:

    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

  3. agsmith Says:

    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).

  4. Josh Smith Says:

    Thanks. That makes sense.

  5. Neelima Says:

    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.

  6. Neelima Says:

    Correction to my above comment

    The reason being, the way you attach the event in weakeventmanager is

    source.Event += DeliverEvent;

  7. agsmith Says:

    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.

  8. Bob Says:

    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

  9. jon Says:

    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);

  10. jon Says:

    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

  11. WPF – Watermark Textbox Behavior | Blindmeis's Blog Says:

    [...] man sich ein MemoryLeak in den Code. Wer das ganze einmal genauer nachlesen will dem sei folgender Post von Adrew Smith ans Herz gelegt. Ich habe den Code einfach übenommen. Die eigentliche Logik ob das [...]

  12. Peter Says:

    You sir, are my HERO

  13. Artem Sovetnikov Says:

    Want to say about DependencyPropertyDescriptor.AddValueChanged in composition with an WeakEventManager pattern.

    As you say, the DependencyPropertyDescriptor.AddValueChanged method roots not only our event handler, but the event source object.

    So with WeakEventManager the event handler can be collected, but the event source remains rooted without call to WeakEventManager RemoveListener(where the RemoveValueChanged need to be called).

    Imagine the situation where the event handler target and the event source is the same objects? The call to RemoveListener(RemoveValueChanged) is mandatory again.

    An WeakEventManager approach is pointless with AddValueChanged.

  14. Утечки в WPF · легкая тема Says:

    [...] Оригинальное решение для AddValueChanged: http://agsmith.wordpress.com/2008/04/07/propertydescriptor-addvaluechanged-alternative/ [...]

  15. Artem Sovetnikov Says:

    I have added a PinToSource(true by default) option to your PropertyChangeNotifier class, that forces it action more like regular weak event, just subscribe to an event and forget.

    In the background, it makes two things, the ValueChanged event becomes weak, and the PropertyChangeNotifier pins himself to an event source object by reference through attached property.

    The code is at code.google.com/p/sovetnikov/source/browse/WPFLib/Misc/WeakEventHelper.cs

  16. Todor Tsvetkov Says:

    Thanks a lot, Andrew!

    I was considering using the “DependencyPropertyDescriptor.AddValueChanged()” approach when I encountered this article. Saved me from a lot of trouble! Great insight into WPF!

  17. Jason Says:

    Thanks for this article!!!! I was using AddValueChanged which was causing a leak. Your class solved it for me :)

  18. Jimme Jardine Says:

    Nice one. It’s always amazing how many little of these GC “hooks” are lurking inside the WPF framework. Thank god for the SciTech and RedGate memory profilers, or http://www.Qiqqa.com would be dead in the water :-)

    One suggestion is that I would recommend that instead of passing in the property name as a string (because it makes refactoring a nightmare), that you remove that constructor and always use the DependencyProperty…

    Thanks again for the post!
    Jimme

  19. In silverlight, how to you attach a changeEvent handler to an inherited dependency property? « « Programmers Goodies Programmers Goodies Says:

    [...] PropertyDescriptor AddValueChanged Alternative [...]

  20. Alexander Says:

    I don’t understand the logic. Now we have leek of instance PropertyChangeNotifier instead of ListBox. Is it better?

  21. Yakir Dorani Says:

    Why is the Dispose method needed here? What if I just keep PropertyNotifier objects in a list and casually remove objects with null PropertySource? Do I really need to call the Dispose method before removing them from the list in that scenario?

  22. Top 3 Memory Leak Inducing Pitfalls of WPF Programming | BugAid for Visual Studio Says:

    [...] WPF handles these sort of problems internally by a vast sprinkling of the Weak Event Design Pattern, and mostly leaves you to fend for yourself when it has caused you to bump against these “by design” memory leaks. How should you fight this evil? Well, my personal recommendation is to avoid using AddValueChanged entirely, and instead use the PropertyChangedNotifier class from Andrew Smith’s blog post. [...]

  23. Price Comparison Says:

    Price Comparison…

    [...]PropertyDescriptor AddValueChanged Alternative « Andrew Smith[...]…

  24. nice one Says:

    nice one…

    [...]PropertyDescriptor AddValueChanged Alternative « Andrew Smith[...]…

  25. csa standards Says:

    csa standards…

    [...]PropertyDescriptor AddValueChanged Alternative « Andrew Smith[...]…

  26. Andrew BT Says:

    Wow, a really nice solution. I have seen this one on the web:
    http://bjorn.kuiper.nu/2011/05/11/tips-tricks-listening-to-dependency-property-change-notifications-of-a-given-element/

    but you cannot re-register the handler for another instance of your watcher. The solution above works not only in WPF & Silverlight but also with multiple registrations / instances. Very nicely done! :)

  27. vipul Says:

    Thanks Andrew!!
    Its works for me !!!!!

  28. http://tinyurl.com/goodshane06734 Says:

    This is really the 4th blog, of yours I personally went through.
    Nevertheless I love this particular one, “PropertyDescriptor AddValueChanged Alternative Andrew Smith” the very best.

    All the best ,Christopher

  29. http://tinyurl.com/weigtough48093 Says:

    What really encouraged you to create “PropertyDescriptor AddValueChanged
    Alternative Andrew Smith”? I actuallyhonestly appreciated it!
    Thanks for your time ,Star

  30. JK Says:

    @ jon – thank you for the tip about keeping a reference to the notifier, you really helped me! I couldn’t figure out why the event would fire once but then wouldn’t fire after that. I guess the notifier was being cleaned up once it went out of scope since I wasn’t keeping any reference to it. Now that I hold a reference to it the even seems to fire every time it’s supposed to.

  31. Blend Behaviors + DependencyPropertyDescriptor = Memory Leak - Programming Tidbits Says:

    [...] 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 [...]

  32. billet d avion bradé Says:

    hello, Mes parents m’ont appellée Marphisa et j’aime beaucoup ce
    prénom.
    Je suis agée de trente-cinq ans Et tant pis si on ne le dit pas .

    Mon métier: fleuriste … Il est dit que je suis une bonne poire.

  33. Monique Hawkins Says:

    Thank you so much, this was causing us such a problem.

    The weird thing was, that we WERE calling RemoveValueChanged – but it simply wasn’t working – not that I can find any reference on the internet anywhere that RemoveValueChanged can fail to do its job.

    We were calling AddValueChanged in the Loaded, and RemoveValueChanged in the Unloaded – I checked they were both called, both the correct numbers of times.

    Even more strangely, we had 2 situations in which the control was Unloaded – one where we were merely switching between tabs in a tabcontrol (both with different datatemplates, hence control unloading), and another where the tabcontrol itself was unloading as well. Our RemoveValueChanged appeared to work when the tabcontrol was unloading, but didn’t work when just moving between tabs of the tabcontrol.

    It bothers me that I was not able to fully understand what the problem was with our calling of RemoveValueChanged, but your solution has fixed our memory leak. Thanks again.

  34. http://www.podomatic.com/ Says:

    People often stay away from the portable audio system fearing that
    they’ll lack the audio quality. The choices endless when you happen to be dealing with home audio speakers which are wireless and good quality. Some wireless systems have the ability to transmit full 1080p high-definition signals.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: