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
}
