Features
- Desktop and Silverlight CLR compatibility
- Capability to perform assignment and raise appropriate events before and after assignment.
- Weak referenced
- Provides for both expression tree and loosely typed strings
- Uses extended EventArgs to supply before and after values
- Extended PropertyChangingEventArgs for cancellable changes
- Configurable to use caching of EventArgs to decrease heap fragmentation
- Comes with unit tests for Desktop and Silverlight CLRs
Introduction
INotifyPropertyChanged is a ubiquitous part of Silverlight and WPF programming. It is used extensively in WPF and Silverlight to enable non-DependencyObjects to signal that a bound value is out of date. I’ve seen many approaches that have been used in order to remove the property name string requirement. Some have employed lambda expressions, or walking the stack, while others have used generated proxies or AOP point cuts. This post and the accompanying code are not so much about ridding us from the loosely typed string, although I do provide the means if you don’t mind a performance hit. Today there is another code smell that I would like to address, and it is the use of base classes for property change notification.
In this post I will demonstrate how we are able to encapsulate two property change interface implementations (INotifyPropertyChanged and INotifyPropertyChanging) in a reusable class, and in a weak referencing manner to avoid any possible leakage.
Using a base class implementation for INotifyPropertyChanged has never sat easy with me. It probably goes back to early 2003 after reading Juval Lowy’s landmark book Programming .NET Components. The principal of favouring aggregation over inheritance is a driver for creating shallow class hierarchies and maintainable components. It is a principal that offers many advantages, and one that I strongly recommend.
Using the Library
DanielVaughan.ComponentModel .PropertyChangeNotifier is the main class. You use it by creating a field in your owner class, and instanciating it within the owner’s constructor. We apply the boiler plate code, which consists of a ‘flow-through’ interface implementation for either or both INotifyPropertyChanged and INotifyPropertyChanging.
An example is shown in the following excerpt.
<!-- code formatted by http://manoli.net/csharpformat/ -->
<span class="kwrd">class</span> MockNotifyPropertyChanged : INotifyPropertyChanged, INotifyPropertyChanging
{
<span class="kwrd">readonly</span> PropertyChangeNotifier notifier;
<span class="preproc">#region</span> INotifyPropertyChanged Implementation
<span class="kwrd">public</span> <span class="kwrd">event</span> PropertyChangedEventHandler PropertyChanged
{
add
{
notifier.PropertyChanged += <span class="kwrd">value</span>;
}
remove
{
notifier.PropertyChanged -= <span class="kwrd">value</span>;
}
}
<span class="preproc">#endregion</span>
<span class="preproc">#region</span> INotifyPropertyChanging Implementation
<span class="kwrd">public</span> <span class="kwrd">event</span> PropertyChangingEventHandler PropertyChanging
{
add
{
notifier.PropertyChanging += <span class="kwrd">value</span>;
}
remove
{
notifier.PropertyChanging -= <span class="kwrd">value</span>;
}
}
<span class="preproc">#endregion</span>
<span class="kwrd">public</span> MockNotifyPropertyChanged()
{
notifier = <span class="kwrd">new</span> PropertyChangeNotifier(<span class="kwrd">this</span>);
}
<span class="kwrd">int</span> int1;
<span class="kwrd">public</span> <span class="kwrd">int</span> TestPropertyAssigned
{
get
{
<span class="kwrd">return</span> int1;
}
set
{
notifier.Assign(
<span class="str">"TestPropertyAssigned"</span>, <span class="kwrd">ref</span> int1, <span class="kwrd">value</span>);
}
}
<span class="kwrd">string</span> string1;
<span class="kwrd">public</span> <span class="kwrd">string</span> TestPropertyAssignedLambda
{
get
{
<span class="kwrd">return</span> string1;
}
set
{
notifier.Assign(
(MockNotifyPropertyChanged mock) => mock.TestPropertyAssignedLambda,
<span class="kwrd">ref</span> string1, <span class="kwrd">value</span>);
}
}
}
The two property examples shown, delegate the task of assigning the property to the PropertyChangeNotifier. This provides the PropertyChangeNotifier with the opportunity to raise the PropertyChanging event of the INotifyPropertyChanging interface, perform the assignment (or return if a handler called Cancel() on the EventArgs, then raise the PropertyChangedEvent from the INotifyPropertyChanged interface.
Implementation
The following excerpt is taken from the PropertyChangeNotifier class. It contains both Silverlight and Desktop CLR specific sections.
<!-- code formatted by http://manoli.net/csharpformat/ -->
<span class="rem"> <span class="kwrd">string</span> propertyName, <span class="kwrd">ref</span> TProperty property, TProperty newValue)
{
<span class="kwrd">if</span> (OwnerDisposed)
{
<span class="kwrd">return</span>;
}
ArgumentValidator.AssertNotNullOrEmpty(propertyName, <span class="str">"propertyName"</span>);
ValidatePropertyName(propertyName);
AssignWithNotificationAux(propertyName, <span class="kwrd">ref</span> property, newValue);
}
<span class="rem">/// <summary></span>
<span class="rem">/// Slow. Not recommended.</span>
<span class="rem">/// Assigns the specified newValue to the specified property</span>
<span class="rem">/// and then notifies listeners that the property has changed.</span>
<span class="rem">/// Assignment nor notification will occur if the specified</span>
<span class="rem">/// property and newValue are equal. </span>
<span class="rem">/// </summary></span>
<span class="rem">/// <typeparam name="T"></typeparam></span>
<span class="rem">/// <typeparam name="TProperty">The type of the property.</typeparam></span>
<span class="rem">/// <param name="expression">The expression that is used to derive the property name.</span>
<span class="rem">/// Should not be <code>null</code>.</param></span>
<span class="rem">/// <param name="property">A reference to the property that is to be assigned.</param></span>
<span class="rem">/// <param name="newValue">The value to assign the property.</param></span>
<span class="rem">/// <exception cref="ArgumentNullException"></span>
<span class="rem">/// Occurs if the specified propertyName is <code>null</code>.</exception></span>
<span class="rem">/// <exception cref="ArgumentException"></span>
<span class="rem">/// Occurs if the specified propertyName is an empty string.</exception></span>
<span class="kwrd">public</span> <span class="kwrd">void</span> Assign<T, TProperty>(
Expression<Func<T, TProperty>> expression, <span class="kwrd">ref</span> TProperty property, TProperty newValue)
{
<span class="kwrd">if</span> (OwnerDisposed)
{
<span class="kwrd">return</span>;
}
<span class="kwrd">string</span> propertyName = GetPropertyName(expression);
AssignWithNotificationAux(propertyName, <span class="kwrd">ref</span> property, newValue);
}
<span class="kwrd">void</span> AssignWithNotificationAux<TProperty>(
<span class="kwrd">string</span> propertyName, <span class="kwrd">ref</span> TProperty property, TProperty newValue)
{
<span class="rem">/* Boxing may occur here. We should consider </span>
<span class="rem"> * providing some overloads for primitives. */</span>
<span class="kwrd">if</span> (Equals(property, newValue))
{
<span class="kwrd">return</span>;
}
<span class="kwrd">if</span> (useExtendedEventArgs)
{
var args = <span class="kwrd">new</span> PropertyChangingEventArgs<TProperty>(propertyName, property, newValue);
OnPropertyChanging(args);
<span class="kwrd">if</span> (args.Cancelled)
{
<span class="kwrd">return</span>;
}
var oldValue = property;
property = newValue;
OnPropertyChanged(<span class="kwrd">new</span> PropertyChangedEventArgs<TProperty>(
propertyName, oldValue, newValue));
}
<span class="kwrd">else</span>
{
var args = RetrieveOrCreatePropertyChangingEventArgs(propertyName);
OnPropertyChanging(args);
var changedArgs = RetrieveOrCreatePropertyChangedEventArgs(propertyName);
OnPropertyChanged(changedArgs);
}
}
<span class="kwrd">readonly</span> Dictionary<<span class="kwrd">string</span>, <span class="kwrd">string</span>> expressions = <span class="kwrd">new</span> Dictionary<<span class="kwrd">string</span>, <span class="kwrd">string</span>>();
<span class="rem">/// <summary></span>
<span class="rem">/// Notifies listeners that the specified property has changed.</span>
<span class="rem">/// </summary></span>
<span class="rem">/// <typeparam name="TProperty">The type of the property.</typeparam></span>
<span class="rem">/// <param name="propertyName">Name of the property. Can not be null.</param></span>
<span class="rem">/// <param name="oldValue">The old value before the change occured.</param></span>
<span class="rem">/// <param name="newValue">The new value after the change occured.</param></span>
<span class="rem">/// <exception cref="ArgumentNullException"></span>
<span class="rem">/// Occurs if the specified propertyName is <code>null</code>.</exception></span>
<span class="rem">/// <exception cref="ArgumentException"></span>
<span class="rem">/// Occurs if the specified propertyName is an empty string.</exception></span>
<span class="kwrd">public</span> <span class="kwrd">void</span> NotifyChanged<TProperty>(
<span class="kwrd">string</span> propertyName, TProperty oldValue, TProperty newValue)
{
<span class="kwrd">if</span> (OwnerDisposed)
{
<span class="kwrd">return</span>;
}
ArgumentValidator.AssertNotNullOrEmpty(propertyName, <span class="str">"propertyName"</span>);
ValidatePropertyName(propertyName);
<span class="kwrd">if</span> (ReferenceEquals(oldValue, newValue))
{
<span class="kwrd">return</span>;
}
var args = useExtendedEventArgs
? <span class="kwrd">new</span> PropertyChangedEventArgs<TProperty>(propertyName, oldValue, newValue)
: RetrieveOrCreatePropertyChangedEventArgs(propertyName);
OnPropertyChanged(args);
}
<span class="rem">/// <summary></span>
<span class="rem">/// Slow. Not recommended.</span>
<span class="rem">/// Notifies listeners that the property has changed.</span>
<span class="rem">/// Notification will occur if the specified</span>
<span class="rem">/// property and newValue are equal. </span>
<span class="rem">/// </summary></span>
<span class="rem">/// <param name="expression">The expression that is used to derive the property name.</span>
<span class="rem">/// Should not be <code>null</code>.</param></span>
<span class="rem">/// <param name="oldValue">The old value of the property before it was changed.</param></span>
<span class="rem">/// <param name="newValue">The new value of the property after it was changed.</param></span>
<span class="rem">/// <exception cref="ArgumentNullException"></span>
<span class="rem">/// Occurs if the specified propertyName is <code>null</code>.</exception></span>
<span class="rem">/// <exception cref="ArgumentException"></span>
<span class="rem">/// Occurs if the specified propertyName is an empty string.</exception></span>
<span class="kwrd">public</span> <span class="kwrd">void</span> NotifyChanged<T, TResult>(
Expression<Func<T, TResult>> expression, TResult oldValue, TResult newValue)
{
<span class="kwrd">if</span> (OwnerDisposed)
{
<span class="kwrd">return</span>;
}
ArgumentValidator.AssertNotNull(expression, <span class="str">"expression"</span>);
<span class="kwrd">string</span> name = GetPropertyName(expression);
NotifyChanged(name, oldValue, newValue);
}
<span class="kwrd">static</span> MemberInfo GetMemberInfo<T, TResult>(Expression<Func<T, TResult>> expression)
{
var member = expression.Body <span class="kwrd">as</span> MemberExpression;
<span class="kwrd">if</span> (member != <span class="kwrd">null</span>)
{
<span class="kwrd">return</span> member.Member;
}
<span class="rem">/* TODO: Make localizable resource. */</span>
<span class="kwrd">throw</span> <span class="kwrd">new</span> ArgumentException(<span class="str">"MemberExpression expected."</span>, <span class="str">"expression"</span>);
}
<span class="preproc">#region</span> INotifyPropertyChanging Implementation
<span class="preproc">#if</span> !SILVERLIGHT
[field: NonSerialized]
<span class="preproc">#endif</span>
<span class="kwrd">event</span> PropertyChangingEventHandler propertyChanging;
<span class="kwrd">public</span> <span class="kwrd">event</span> PropertyChangingEventHandler PropertyChanging
{
add
{
<span class="kwrd">if</span> (OwnerDisposed)
{
<span class="kwrd">return</span>;
}
propertyChanging += <span class="kwrd">value</span>;
}
remove
{
<span class="kwrd">if</span> (OwnerDisposed)
{
<span class="kwrd">return</span>;
}
propertyChanging -= <span class="kwrd">value</span>;
}
}
<span class="rem">/// <summary></span>
<span class="rem">/// Raises the <see cref="E:PropertyChanging"/> event.</span>
<span class="rem">/// If the owner has been GC'd then the event will not be raised.</span>
<span class="rem">/// </summary></span>
<span class="rem">/// <param name="e">The <see cref="System.ComponentModel.PropertyChangingEventArgs"/> </span>
<span class="rem">/// instance containing the event data.</param></span>
<span class="kwrd">void</span> OnPropertyChanging(PropertyChangingEventArgs e)
{
var owner = ownerWeakReference.Target;
<span class="kwrd">if</span> (owner != <span class="kwrd">null</span> && propertyChanging != <span class="kwrd">null</span>)
{
propertyChanging(owner, e);
}
}
<span class="preproc">#endregion</span>
<span class="preproc">#if</span> SILVERLIGHT
<span class="kwrd">readonly</span> <span class="kwrd">object</span> expressionsLock = <span class="kwrd">new</span> <span class="kwrd">object</span>();
<span class="kwrd">string</span> GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
{
<span class="kwrd">string</span> name;
<span class="kwrd">lock</span> (expressionsLock)
{
<span class="kwrd">if</span> (!expressions.TryGetValue(expression.ToString(), <span class="kwrd">out</span> name))
{
<span class="kwrd">if</span> (!expressions.TryGetValue(expression.ToString(), <span class="kwrd">out</span> name))
{
var memberInfo = GetMemberInfo(expression);
<span class="kwrd">if</span> (memberInfo == <span class="kwrd">null</span>)
{
<span class="rem">/* TODO: Make localizable resource. */</span>
<span class="kwrd">throw</span> <span class="kwrd">new</span> InvalidOperationException(<span class="str">"MemberInfo not found."</span>);
}
name = memberInfo.Name;
expressions.Add(expression.ToString(), name);
}
}
}
<span class="kwrd">return</span> name;
}
<span class="preproc">#else</span>
<span class="kwrd">readonly</span> ReaderWriterLockSlim expressionsLock = <span class="kwrd">new</span> ReaderWriterLockSlim();
<span class="kwrd">string</span> GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
{
<span class="kwrd">string</span> name;
expressionsLock.EnterUpgradeableReadLock();
<span class="kwrd">try</span>
{
<span class="kwrd">if</span> (!expressions.TryGetValue(expression.ToString(), <span class="kwrd">out</span> name))
{
expressionsLock.EnterWriteLock();
<span class="kwrd">try</span>
{
<span class="kwrd">if</span> (!expressions.TryGetValue(expression.ToString(), <span class="kwrd">out</span> name))
{
var memberInfo = GetMemberInfo(expression);
<span class="kwrd">if</span> (memberInfo == <span class="kwrd">null</span>)
{
<span class="rem">/* TODO: Make localizable resource. */</span>
<span class="kwrd">throw</span> <span class="kwrd">new</span> InvalidOperationException(<span class="str">"MemberInfo not found."</span>);
}
name = memberInfo.Name;
expressions.Add(expression.ToString(), name);
}
}
<span class="kwrd">finally</span>
{
expressionsLock.ExitWriteLock();
}
}
}
<span class="kwrd">finally</span>
{
expressionsLock.ExitUpgradeableReadLock();
}
<span class="kwrd">return</span> name;
}
<span class="preproc">#endif</span>
<span class="kwrd">bool</span> cleanupOccured;
<span class="kwrd">bool</span> OwnerDisposed
{
get
{
<span class="rem">/* We slightly improve performance here </span>
<span class="rem"> * by avoiding multiple Owner property calls </span>
<span class="rem"> * after the Owner has been disposed. */</span>
<span class="kwrd">if</span> (cleanupOccured)
{
<span class="kwrd">return</span> <span class="kwrd">true</span>;
}
var owner = Owner;
<span class="kwrd">if</span> (owner != <span class="kwrd">null</span>)
{
<span class="kwrd">return</span> <span class="kwrd">false</span>;
}
cleanupOccured = <span class="kwrd">true</span>;
var changedSubscribers = propertyChanged.GetInvocationList();
<span class="kwrd">foreach</span> (var subscriber <span class="kwrd">in</span> changedSubscribers)
{
propertyChanged -= (PropertyChangedEventHandler)subscriber;
}
var changingSubscribers = propertyChanging.GetInvocationList();
<span class="kwrd">foreach</span> (var subscriber <span class="kwrd">in</span> changingSubscribers)
{
propertyChanging -= (PropertyChangingEventHandler)subscriber;
}
<span class="rem">/* Events should be null at this point. Nevertheless... */</span>
propertyChanged = <span class="kwrd">null</span>;
propertyChanging = <span class="kwrd">null</span>;
propertyChangedEventArgsCache.Clear();
propertyChangingEventArgsCache.Clear();
<span class="kwrd">return</span> <span class="kwrd">true</span>;
}
}
[Conditional(<span class="str">"DEBUG"</span>)]
<span class="kwrd">void</span> ValidatePropertyName(<span class="kwrd">string</span> propertyName)
{
<span class="preproc">#if</span> !SILVERLIGHT
var propertyDescriptor = TypeDescriptor.GetProperties(Owner)[propertyName];
<span class="kwrd">if</span> (propertyDescriptor == <span class="kwrd">null</span>)
{
<span class="rem">/* TODO: Make localizable resource. */</span>
<span class="kwrd">throw</span> <span class="kwrd">new</span> Exception(<span class="kwrd">string</span>.Format(
<span class="str">"The property '{0}' does not exist."</span>, propertyName));
}
<span class="preproc">#endif</span>
}
<span class="kwrd">bool</span> useExtendedEventArgs;
<span class="kwrd">readonly</span> Dictionary<<span class="kwrd">string</span>, PropertyChangedEventArgs> propertyChangedEventArgsCache = <span class="kwrd">new</span> Dictionary<<span class="kwrd">string</span>, PropertyChangedEventArgs>();
<span class="kwrd">readonly</span> Dictionary<<span class="kwrd">string</span>, PropertyChangingEventArgs> propertyChangingEventArgsCache = <span class="kwrd">new</span> Dictionary<<span class="kwrd">string</span>, PropertyChangingEventArgs>();
<span class="preproc">#if</span> SILVERLIGHT
<span class="kwrd">readonly</span> <span class="kwrd">object</span> propertyChangingEventArgsCacheLock = <span class="kwrd">new</span> <span class="kwrd">object</span>();
PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(<span class="kwrd">string</span> propertyName)
{
var result = RetrieveOrCreateEventArgs(
propertyName,
propertyChangingEventArgsCacheLock,
propertyChangingEventArgsCache,
x => <span class="kwrd">new</span> PropertyChangingEventArgs(x));
<span class="kwrd">return</span> result;
}
<span class="kwrd">readonly</span> <span class="kwrd">object</span> propertyChangedEventArgsCacheLock = <span class="kwrd">new</span> <span class="kwrd">object</span>();
PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(<span class="kwrd">string</span> propertyName)
{
var result = RetrieveOrCreateEventArgs(
propertyName,
propertyChangedEventArgsCacheLock,
propertyChangedEventArgsCache,
x => <span class="kwrd">new</span> PropertyChangedEventArgs(x));
<span class="kwrd">return</span> result;
}
<span class="kwrd">static</span> TArgs RetrieveOrCreateEventArgs<TArgs>(
<span class="kwrd">string</span> propertyName, <span class="kwrd">object</span> cacheLock, Dictionary<<span class="kwrd">string</span>, TArgs> argsCache,
Func<<span class="kwrd">string</span>, TArgs> createFunc)
{
ArgumentValidator.AssertNotNull(propertyName, <span class="str">"propertyName"</span>);
TArgs result;
<span class="kwrd">lock</span> (cacheLock)
{
<span class="kwrd">if</span> (argsCache.TryGetValue(propertyName, <span class="kwrd">out</span> result))
{
<span class="kwrd">return</span> result;
}
result = createFunc(propertyName);
argsCache[propertyName] = result;
}
<span class="kwrd">return</span> result;
}
<span class="preproc">#else</span>
<span class="kwrd">readonly</span> ReaderWriterLockSlim propertyChangedEventArgsCacheLock = <span class="kwrd">new</span> ReaderWriterLockSlim();
PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(<span class="kwrd">string</span> propertyName)
{
ArgumentValidator.AssertNotNull(propertyName, <span class="str">"propertyName"</span>);
var result = RetrieveOrCreateArgs(
propertyName,
propertyChangedEventArgsCache,
propertyChangedEventArgsCacheLock,
x => <span class="kwrd">new</span> PropertyChangedEventArgs(x));
<span class="kwrd">return</span> result;
}
<span class="kwrd">readonly</span> ReaderWriterLockSlim propertyChangingEventArgsCacheLock = <span class="kwrd">new</span> ReaderWriterLockSlim();
<span class="kwrd">static</span> TArgs RetrieveOrCreateArgs<TArgs>(<span class="kwrd">string</span> propertyName, Dictionary<<span class="kwrd">string</span>, TArgs> argsCache,
ReaderWriterLockSlim lockSlim, Func<<span class="kwrd">string</span>, TArgs> createFunc)
{
ArgumentValidator.AssertNotNull(propertyName, <span class="str">"propertyName"</span>);
TArgs result;
lockSlim.EnterUpgradeableReadLock();
<span class="kwrd">try</span>
{
<span class="kwrd">if</span> (argsCache.TryGetValue(propertyName, <span class="kwrd">out</span> result))
{
<span class="kwrd">return</span> result;
}
lockSlim.EnterWriteLock();
<span class="kwrd">try</span>
{
<span class="kwrd">if</span> (argsCache.TryGetValue(propertyName, <span class="kwrd">out</span> result))
{
<span class="kwrd">return</span> result;
}
result = createFunc(propertyName);
argsCache[propertyName] = result;
<span class="kwrd">return</span> result;
}
<span class="kwrd">finally</span>
{
lockSlim.ExitWriteLock();
}
}
<span class="kwrd">finally</span>
{
lockSlim.ExitUpgradeableReadLock();
}
}
PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(<span class="kwrd">string</span> propertyName)
{
ArgumentValidator.AssertNotNull(propertyName, <span class="str">"propertyName"</span>);
var result = RetrieveOrCreateArgs(
propertyName,
propertyChangingEventArgsCache,
propertyChangingEventArgsCacheLock,
x => <span class="kwrd">new</span> PropertyChangingEventArgs(x));
<span class="kwrd">return</span> result;
}
<span class="preproc">#endif</span>
}
I’ve extended the PropertyChangedEventArgs and the PropertyChangingEventArgs to include before and after values. The following excerpt shows the PropertyChangedEventArgs.
<!-- code formatted by http://manoli.net/csharpformat/ -->
<span class="rem">{
<span class="rem">
INotifyPropertyChanging doesn’t exist in Silverlight, so I’ve implemented it.
In order to turn of the extended EventArgs, pass an extra argument to the constructor. By turning of the extended EventArgs, we enable to arg caching feature. I implemented this after reading Josh Smith’s nice articles on the subject.
The PropertyChangeNotifier retains a link to the Owner using a WeakReference. Each time a change is handled, the PropertyChangeNotifier checks to see if the Owner is still alive. If it isn’t, the PropertyChangeNotifier will remove all event handlers.
Unit Tests
The download includes various unit tests for both the Desktop and Silverlight environments. I recommend examining them, to further your understanding about how it all works.

Figure: Desktop CLR test results.

Figure: Silverlight CLR test results.
Future Enhancements
- Batch support
- Event Suppression
Download source code for the Desktop and Silverlight CLRs: Core_01_01.zip (1.42 mb)