Click here to Skip to main content
Click here to Skip to main content

Property Change Notification using a Weak Referencing Strategy

By , 29 Aug 2009
 

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">/// <summary></span>
<span class="rem">/// This class provides an implementation of the <see cref="INotifyPropertyChanged"/></span>
<span class="rem">/// and <see cref="INotifyPropertyChanging"/> interfaces. </span>
<span class="rem">/// Extended <see cref="PropertyChangedEventArgs"/> and <see cref="PropertyChangingEventArgs"/></span>
<span class="rem">/// are used to provides the old value and new value for the property. </span>
<span class="rem">/// <seealso cref="PropertyChangedEventArgs{TProperty}"/></span>
<span class="rem">/// <seealso cref="PropertyChangingEventArgs{TProperty}"/></span>
<span class="rem">/// </summary></span>
<span class="preproc">#if</span> !SILVERLIGHT
[Serializable]
<span class="preproc">#endif</span>
<span class="kwrd">public</span> <span class="kwrd">sealed</span> <span class="kwrd">class</span> PropertyChangeNotifier : INotifyPropertyChanged, INotifyPropertyChanging
{
    <span class="kwrd">readonly</span> WeakReference ownerWeakReference;

    <span class="rem">/// <summary></span>
    <span class="rem">/// Gets the owner for testing purposes.</span>
    <span class="rem">/// </summary></span>
    <span class="rem">/// <value>The owner.</value></span>
    <span class="kwrd">internal</span> <span class="kwrd">object</span> Owner
    {
        get
        {
            <span class="kwrd">if</span> (ownerWeakReference.Target != <span class="kwrd">null</span>)
            {
                <span class="kwrd">return</span> ownerWeakReference.Target;
            }
            <span class="kwrd">return</span> <span class="kwrd">null</span>;
        }
    }

    <span class="rem">/// <summary></span>
    <span class="rem">/// Initializes a new instance </span>
    <span class="rem">/// of the <see cref="PropertyChangeNotifier"/> class.</span>
    <span class="rem">/// </summary></span>
    <span class="rem">/// <param name="owner">The intended sender </span>
    <span class="rem">/// of the <code>PropertyChanged</code> event.</param></span>
    <span class="kwrd">public</span> PropertyChangeNotifier(<span class="kwrd">object</span> owner) : <span class="kwrd">this</span>(owner, <span class="kwrd">true</span>)
    {
    }

    <span class="rem">/// <summary></span>
    <span class="rem">/// Initializes a new instance </span>
    <span class="rem">/// of the <see cref="PropertyChangeNotifier"/> class.</span>
    <span class="rem">/// </summary></span>
    <span class="rem">/// <param name="owner">The intended sender </span>
    <span class="rem">/// <param name="useExtendedEventArgs">If <c>true</c> the</span>
    <span class="rem">/// generic <see cref="PropertyChangedEventArgs{TProperty}"/></span>
    <span class="rem">/// and <see cref="PropertyChangingEventArgs{TProperty}"/> </span>
    <span class="rem">/// are used when raising events. </span>
    <span class="rem">/// Otherwise, the non-generic types are used, and they are cached </span>
    <span class="rem">/// to decrease heap fragmentation.</param></span>
    <span class="rem">/// of the <code>PropertyChanged</code> event.</param></span>
    <span class="kwrd">public</span> PropertyChangeNotifier(<span class="kwrd">object</span> owner, <span class="kwrd">bool</span> useExtendedEventArgs)
    {
        ArgumentValidator.AssertNotNull(owner, <span class="str">"owner"</span>);
        ownerWeakReference = <span class="kwrd">new</span> WeakReference(owner);
        <span class="kwrd">this</span>.useExtendedEventArgs = useExtendedEventArgs;
    }

    <span class="preproc">#region</span> <span class="kwrd">event</span> PropertyChanged

<span class="preproc">#if</span> !SILVERLIGHT
    [field: NonSerialized]
<span class="preproc">#endif</span>
    <span class="kwrd">event</span> PropertyChangedEventHandler propertyChanged;

    <span class="rem">/// <summary></span>
    <span class="rem">/// Occurs when a property value changes.</span>
    <span class="rem">/// </summary></span>
    <span class="kwrd">public</span> <span class="kwrd">event</span> PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            <span class="kwrd">if</span> (OwnerDisposed)
            {
                <span class="kwrd">return</span>;
            }
            propertyChanged += <span class="kwrd">value</span>;
        }
        remove
        {
            <span class="kwrd">if</span> (OwnerDisposed)
            {
                <span class="kwrd">return</span>;
            }
            propertyChanged -= <span class="kwrd">value</span>;
        }
    }

    <span class="rem">/// <summary></span>
    <span class="rem">/// Raises the <see cref="E:PropertyChanged"/> 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.PropertyChangedEventArgs"/> </span>
    <span class="rem">/// instance containing the event data.</param></span>
    <span class="kwrd">void</span> OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var owner = ownerWeakReference.Target;
        <span class="kwrd">if</span> (owner != <span class="kwrd">null</span> && propertyChanged != <span class="kwrd">null</span>)
        {
            propertyChanged(owner, e);
        }
    }

    <span class="preproc">#endregion</span>

    <span class="rem">/// <summary></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">/// </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="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<TProperty>(
        <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">/// <summary></span>
<span class="rem">/// Provides data for the <see cref="INotifyPropertyChanged.PropertyChanged"/> event,</span>
<span class="rem">/// exposed via the <see cref="PropertyChangeNotifier"/>.</span>
<span class="rem">/// </summary></span>
<span class="rem">/// <typeparam name="TProperty">The type of the property.</typeparam></span>
<span class="kwrd">public</span> <span class="kwrd">sealed</span> <span class="kwrd">class</span> PropertyChangedEventArgs<TProperty> : PropertyChangedEventArgs
{
    <span class="rem">/// <summary></span>
    <span class="rem">/// Gets the value of the property before it was changed.</span>
    <span class="rem">/// </summary></span>
    <span class="rem">/// <value>The old value.</value></span>
    <span class="kwrd">public</span> TProperty OldValue { get; <span class="kwrd">private</span> set; }
        <span class="rem">/// <summary></span>
    <span class="rem">/// Gets the new value of the property after it was changed.</span>
    <span class="rem">/// </summary></span>
    <span class="rem">/// <value>The new value.</value></span>
    <span class="kwrd">public</span> TProperty NewValue { get; <span class="kwrd">private</span> set; }
        <span class="rem">/// <summary></span>
    <span class="rem">/// Initializes a new instance </span>
    <span class="rem">/// of the <see cref="PropertyChangedEventArgs{TProperty}"/> class.</span>
    <span class="rem">/// </summary></span>
    <span class="rem">/// <param name="propertyName">Name of the property that changed.</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="kwrd">internal</span> PropertyChangedEventArgs(
        <span class="kwrd">string</span> propertyName, TProperty oldValue, TProperty newValue) 
        : <span class="kwrd">base</span>(propertyName)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}

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)

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

Daniel Vaughan
Software Developer (Senior) Outcoder
Switzerland Switzerland
Member
Daniel Vaughan is a Microsoft MVP and cofounder of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular Silverlight, WPF, WinRT, and Windows Phone.
 
Daniel is the author of Windows Phone 7.5 Unleashed, the first comprehensive, start-to-finish developer's guide to Microsoft's Windows Phone 7.5.
 
Daniel is also the creator of a number of open-source projects, including Calcium SDK, and Clog.
 
Would you like Daniel to bring value to your organisation? Please contact

Daniel's Blog | MVP profile | Follow on Twitter
 
Windows Phone Experts

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralUsage ExamplememberEng. Jalal4 Nov '09 - 0:25 
GeneralRe: Usage ExamplememberDaniel Vaughan4 Nov '09 - 4:16 
GeneralRe: Usage ExamplememberEng. Jalal5 Nov '09 - 1:00 
GeneralRe: Usage ExamplememberDaniel Vaughan5 Nov '09 - 1:09 
GeneralRe: Usage ExamplememberEng. Jalal5 Nov '09 - 1:25 
GeneralRe: Usage ExamplememberDaniel Vaughan5 Nov '09 - 1:34 
GeneralRe: Usage ExamplememberDaniel Vaughan5 Nov '09 - 1:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130513.1 | Last Updated 29 Aug 2009
Article Copyright 2009 by Daniel Vaughan
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid