One way to avoid messy PropertyChanged event handling

EDIT: Shortly after publishing this blog post, I received some excellent feedback on it, and updated the PropertyObserver class to implement IWeakEventListener.  Special thanks go to Huseyin Tufekcilerli for posting an IWeakEventListener implementation, which got me 90% of the way toward a better solution. This blog post and its source code download have been updated to reflect this improvement.

When working with objects that implement INotifyPropertyChanged, as most ViewModel objects tend to do, it often becomes necessary to attach a handler to their PropertyChanged event.  Your code can quickly accumulate many event handling methods full of conditional statements that check if e.PropertyName equals some hard-coded string value.  This leads to messy, brittle code that becomes difficult to maintain as the mess gets larger.

To combat this evil, I decided to mechanize as much of this as possible.  I created a generic class called PropertyObserver<TPropertySource> which monitors the PropertyChanged event of an object, and executes whatever callback methods you have registered for when certain properties have changed.  In addition, that class will verify that every property name you register with it is a real, public property on the object being observed, because the way that you provide the property  name is checked by the compiler (you pass it a lambda expression that references the property).  If a property name changes, but you forgot to update your code that registers the property with PropertyObserver, you will get a compiler error next time you try to build your solution.

Here are the two methods of interest:

diagram

Here is a simple example, from the source code download, that shows how to use PropertyObserver:

usage_with_lambda

You can download the source code here.  Note: Change the file extension from .DOC to .ZIP and then decompress it.

About these ads

17 Responses to One way to avoid messy PropertyChanged event handling

  1. [...] One way to avoid messy PropertyChanged event handling (Josh Smith) [...]

  2. Josh Smith says:

    Thanks so much, Huseyin! Great idea. I took your IWeakEventListener implementation, and then made PropertyObserver reference the property source object with a WeakReference. Now there is no potential for having a memory leak when using PropertyObserver! :)

    The source code download has been updated to reflect these changes.

    Thanks,
    Josh

  3. sacha says:

    Very nice agent Smith

  4. WeakReference to property source object, that’s even better! I have tweaked the latest project a bit and made use of Lamda Expressions for registering/unregistering properties. Now it is possible to register a property without so called “magic strings”:

    _observer = new PropertyObserver(this.Number)
    .RegisterHandler(n => n.Value, n => Log(“Value: ” + n.Value))
    .RegisterHandler(n => n.IsNegative, this.AppendIsNegative)
    .RegisterHandler(n => n.IsEven, this.AppendIsEven);

    I think this makes it obsolete the call to the “VerifyPropertyName” since it is always checked by the compiler.

    This is based on Einar Ingebrigtsen’s code here: http://www.ingebrigtsen.info/post/2009/07/11/Extensions-and-Helpers-for-Silverlight-and-WPF.aspx

    The latest package is here:

    http://cid-aeeb3ce46c019948.skydrive.live.com/self.aspx/Public/PropertyObserverDemo-LambdaPropertyNames.zip

  5. Johan O says:

    Good post Josh!

    However I have a question. I had alot of these events inside my view models before. Mainly when different view models needed to communicate between each other I would give the view models references to each other and then hook an event handler to the PropertyChanged event. But after I started using your Mediator v2 code (which I absolutely love!) I have not needed any more of these event handlers. So by using the Mediator pattern instead, would this still be of much use?

  6. Josh Smith says:

    @Huseyin – You are the man! Great feature. I updated the source code and blog post again to use the lambda-based approach for specifying the property name. Thanks!! :D

    @Johan – The PropertyObserver and Mediator classes have a very similar role in life. However, Mediator assumes that you control all parties involved. If you are working with classes that you cannot modify the source code of, you might have to rely on PropertyChanged because it doesn’t use your Mediator. Also, you could use these two classes together…have the PropertyObserver wait for property changes, and then broadcast a Mediator message in response. It mostly just boils down to personal preference, in my opinion.

  7. Andrew Smith says:

    Very cool idea. A couple of minor observations:
    1) GetPropertyName could be static since it doesn’t need any member info.
    2) You may want to assert in RegisterHandler if there is an entry for a given propertyName. It may not be obvious to the user of the class that you can only register 1 handler for a given property and any subsequent registration for that property will just overwrite the handler that is invoked.
    3) If a handler unregisters within the callback you could get an invalidoperationexception if the handler was invoked because of a “” property change notification because you are iterating the Values directly. I.e. the user is manipulating the dictionary while you are iterating it. So you may want to copy the Values to an array first and iterate that.

    Again, these are minor and unlikely to come up but I thought I would mention it anyway. Nice work!

  8. Josh Smith says:

    Excellent feedback, Andrew! Thanks so much.

  9. We’ve been using something similar for quite sometime, and it’s the core of the reactive programming framework I’ve been putting together. Checkout the ReactiveObject I put in Continuous Linq. It accomplishes this but uses a static registration, and relies on CLINQ’s very efficient lambda analysis and caching. Also, all subscriptions are weak.

  10. Also, don’t use IWeakEventListener. WPF’s implementation of weak events is slow as crap, and does not scale. They schedule a cleanup on the dispatcher every time a new subscription is made, which is why I had to rewrite it for CLINQ.

  11. Andrew Smith says:

    Are you sure about the WeakEventManager’s cleanup? In looking at the ScheduleCleanup method, they only dispatch a method on the first call to ScheduleCleanup and then wouldn’t dispatch another until the next registration that occurs after that dispatched operation is processed.

  12. Ido Ran says:

    Hello,
    I’m using your PropertyObserver class, but there is one thing I think you left out.
    In the OnPropertyChanged method you should check if the e.PropertyName is empty string and if so invoke all the handlers because when to much is changed you can just fire PropertyChanged with empty string and everything should be updated.

    Something like this:

    if (string.IsNullOrEmpty(e.PropertyName)) {
    // Invoke all the handlers since no property was specified.
    foreach (var handler in _propertyNameToHandlerMap.Values) {
    handler(_propertySource);
    }
    }
    else if (_propertyNameToHandlerMap.ContainsKey(e.PropertyName))
    _propertyNameToHandlerMap[e.PropertyName](_propertySource);

  13. Terry Bondy says:

    Hi Josh,

    I always enjoy and appreciate your work. I realize that you are trying to describe a mechanism on the consumer-side of INotifyPropertyChanged communication, but I can’t help but start to worry when I see something like the following in the source of the notifications:

    if (wasNegative != this.IsNegative)
    this.OnPropertyChanged(“IsNegative”);

    There is a maintainability issue here, especially with the refactor capability of VS. You have partially addressed this in your MVVM DemoApp with calling:

    this.VerifyPropertyName(propertyName);

    in OnPropertyChanged in the source of the notifications, which only runs in debug builds.

    While I can’t offer it at the moment, it would great if the source-side problem had an improvement along the lines of what you have presented in RegisterHandler of this project, say by passing an instance of a class created at source class static constructor time to OnPropertyChanged. A string for the property could be created at class static constructor time (and hence once only) and not require the performance hit during every call during debugging.

  14. Yes, I’m sure that the WeakEventManager cleans up all the time. It was 30% of our CPU time when I profiled it, and hence, I had to rewrite it.

    Also, the other problem here is that the code is ran each time the object is created, rather than once. In ReactiveObject, the registration is done once per type, in each types static constructor. This means that it’s only a one time hit.

  15. Sorry for the spam. Also, by having the handlers reference “this” you are creating a closure for each registration.

  16. [...] to CLINQ a couple months ago.  I noticed that Josh Smith, (who has an excellent WPF blog)  just posted something along the same spirit.  Of course, I quickly spammed the comments in his blog about [...]

Follow

Get every new post delivered to your Inbox.

Join 287 other followers

%d bloggers like this: