I came across this post on Marlon Grech’s blog. Marlon has written a very simple wrapper for registering and unregistering INotifyPropertyChanged event handlers. I modified his solution slightly, doing caching the results of the PropertyInfo resolution instead of recalculating it every time in the PropertyChangedSubscriber.
Here is my version of the PropertyChangedSubscriber.
/// <summary> /// Shortcut to subscribe to PropertyChanged on an INotfiyPropertyChanged and executes an action when that happens /// </summary> /// <typeparam name="TSource">Must implement INotifyPropertyChanged</typeparam> /// <typeparam name="TProperty">Can be any type</typeparam> public class PropertyChangedSubscriber<TSource, TProperty> : IDisposable where TSource : class, INotifyPropertyChanged { private readonly PropertyInfo _propertyInfo; private readonly TSource _source; private Action<TSource> _onChange; public PropertyChangedSubscriber(TSource source, Expression<Func<TSource, TProperty>> property) { var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo; if (propertyInfo == null) { throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); } _propertyInfo = propertyInfo; _source = source; source.PropertyChanged += SourcePropertyChanged; } private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e) { if (IsTargetProperty(e.PropertyName)) { _onChange(sender as TSource); } } /// <summary> /// Executes the action and returns an IDisposable so that you can unregister /// </summary> /// <param name="onChanged">The action to execute</param> /// <returns>The IDisposable so that you can unregister</returns> public IDisposable Do(Action<TSource> onChanged) { _onChange = onChanged; return this; } /// <summary> /// Executes the action only once and automatically unregisters /// </summary> /// <param name="onChanged">The action to be executed</param> public void DoOnce(Action<TSource> onChanged) { Action<TSource> dispose = x => Dispose(); _onChange = (Action<TSource>)Delegate.Combine(onChanged, dispose); } private bool IsTargetProperty(string propertyName) { return _propertyInfo.Name == propertyName; } #region Implementation of IDisposable /// <summary> /// Unregisters the property /// </summary> public void Dispose() { _source.PropertyChanged -= SourcePropertyChanged; } #endregion }