Daniel Vaughan

free range code

Knockout Style Observables in XAML

clock July 15, 2012 11:22 by author Daniel Vaughan

This week there was a short discussion on the WPF Disciples about Knockout observables and the implications of field level and owner level data bindings. The discussion can be found in the guts of this post.

I wanted to explore the topic further, and I decided to take a stab at implementing something analogous to Knockout Observables and Computed Observables in WPF.

In this article we look at implementing field level change notification in WPF, and how a Lambda Expression can be used to specify a composite property that raises change notifications automatically whenever an associated property changes.

To get started, let’s first take a look at a class that illustrates how composite binding are ordinarily done in a viewmodel designed for XAML (see Listing 1). It’s a simple viewmodel that contains three properties. Two properties have backing fields, while the third (FullName) is a composite property; combining the FirstName and LastName properties. When either of the non-composite properties change, change notification has to occur for the composite as well as non-composite properties.

The downside of this approach is that as the complexity of the viewmodel increases and dependencies creep in, you end up having to raise PropertyChanged events for multiple dependent properties. While this example may seem dead simple (it is), over time potential faults may creep in because of this kind of interdependence.

Listing 1. MainWindowViewModelTraditional Class

class MainWindowViewModelTraditional : INotifyPropertyChanged
{
    string firstName;
        
    public string FirstName
    {
        get
        {
            return firstName;
        }
        set
        {
            if (firstName != value)
            {
                firstName = value;
                OnPropertyChanged("FirstName");
                OnPropertyChanged("FullName");
            }
        }
    }

    string lastName;

    public string LastName
    {
        get
        {
            return lastName;
        }
        set
        {
            if (lastName != value)
            {
                lastName = value;
                OnPropertyChanged("LastName");
                OnPropertyChanged("FullName");
            }
        }
    }

    public string FullName
    {
        get
        {
            return firstName + " " + lastName;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Taking the Knockout JS Approach

I’ve been using Knockout quite a bit lately and I like the flexibility and versatility that the binding expression interpreter offers. Knockout allows you to combine observable values in a way that allows you to forego the kind of artificial dependence seen earlier. For example, on a web page you can produce the same string as the FullName property seen in the last example using something like the following:

<span data-bind="text: firstName() + ' ' + lastName()"></span>

You can also achieve the same result using computed values in your viewmodel if you so desire.

In this article I’ve set out to see whether I could come up with something that emulates Knockout's field level notifications in observables and computed observables. I’m quite happy with the result and I believe it has potential. Stay tuned, because at the end of this article you will see how to create a computed property in C# like the following, which raises change notifications automatically:

readonly ComputedValue<string> fullName;

public ComputedValue<string> FullName
{
    get
    {
        return fullName;
    }
}

public MainWindowViewModel()
{
    fullName = new ComputedValue<string>(() => FirstName.Value + " " + ToUpper(LastName.Value));
}

Bridging the HTML-XAML Ravine

In WPF, when using the INotifyPropertyChanged approach to data binding, change notification occurs at the owner object level. The owner is the source property in a data binding, which is ordinarily the DataContext of your control. If you’re using the MVVM pattern this is likely your viewmodel or a nested property. It is the owner object of the property that has the responsibility of raising a PropertyChanged event whenever the property changes.

Conversely, field level notification is where the source of the notification is the field itself (or in XAML, a property). The main advantage of field level notification is that your viewmodel does not need to contain any plumbing code for change notification. Field level notification isn’t achievable in XAML without defining an event for each property in your viewmodel. (Thanks to Bill Kemph for reminding me of field level notifications using dedicated events). This approach is usually avoided because it adds an undue amount of plumbing code to your viewmodel. A wrapper layer is presented in this article, which takes care of raising property changed events for you.

Two classes exist in the downloadable code that emulate the behaviour of Knockout’s observable and computed observable types: ObservableValue and ComputedValue. Both implement INotifyPropertyChanged and automatically raise the PropertyChanged event when an associated value has changed.

The ObservableValue implementation is rudimentary. Things get more interesting when we look at the ComputedValue implementation that allows you to specify a LINQ expression, which is parsed by the ComputedValue object to locate objects that implement INotifyPropertyChanged. When any such object changes, the computed value is recalculated. This allows you to create arbitrary composite properties in your viewmodel that respond to change notifications of any other associated object.

Understanding the ObservableValue Class

An ObservableValue property is defined much like a property using the traditional approach to INPC. The difference is that an ObservableValue instance wraps the value, as shown in the following excerpt:

class MainWindowViewModel
{
    readonly ObservableValue<string> firstName = new ObservableValue<string>("Alan");

    public ObservableValue<string> FirstName
    {
        get
        {
            return firstName;
        }
    }
…
}

To set the value, the ObservableValue.Value property is used. When the setter is used the PropertyChanged event is raised (see Listing 2).

Listing 2. ObservableValue Class

public class ObservableValue<T> : INotifyPropertyChanged
{
    T valueField;

    public T Value
    {
        get
        {
            return valueField;
        }
        set
        {
            if (!EqualityComparer<T>.Default.Equals(valueField, value))
            {
                valueField = value;
                OnPropertyChanged("Value");
            }
        }
    }

    public ObservableValue(T value = default(T))
    {
        Value = value;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Creating Composite Properties with the ComputedValue Class

Unlike ObservableValue, ComputedValue accepts a Lambda Expression that, when compiled, returns a value for the property. Listing 3 shows how the ComputedValue's constructor uses a specified Lamdba Expression.

Listing 3. ComputedValue Class (Excerpt)

public class ComputedValue<T> : INotifyPropertyChanged, IDisposable
{
…
    readonly Func<T> valueFunc;

    public T Value
    {
        get
        {
            T result = valueFunc();
            return result;
        }
    }

    public ComputedValue(Expression<Func<T>> expression)
    {
        if (expression == null)
        {
            throw new ArgumentNullException("expression");
        }

        Expression body = expression.Body;

        ProcessDependents(body);

        valueFunc = expression.Compile();
    }
…
}

When an instance of ComputedValue is created, the specified expression is parsed recursively. Nested MemberExpressions that refer to objects implementing INotifyPropertyChanged are identified (see Listing 4).

Listing 4. ProcessDependents (Excerpt)

void ProcessDependents(Expression expression)
{
    switch (expression.NodeType)
    {
        case ExpressionType.MemberAccess:
            ProcessMemberAccessExpression((MemberExpression)expression);
            break;
        case ExpressionType.Call:
            ProcessMethodCallExpression((MethodCallExpression)expression);
            break;
...
        default:
            return;
    }
}

When a member expression is discovered, its owner is resolved using the ComputedValue.GetValue method. The class subscribes to the PropertyChanged event of the owner object. A PropertyChangedEventHandler is created and placed in a collection along with a WeakReference to the owner object (see Listing 5). When a change notification is received, the ComputedValue object raises its own PropertyChanged event.

Listing 5. ProcessMemberAccessExpression Method

void ProcessMemberAccessExpression(MemberExpression expression)
{
    Expression ownerExpression = expression.Expression;
    Type ownerExpressionType = ownerExpression.Type;
                                            
    if (typeof(INotifyPropertyChanged).IsAssignableFrom(ownerExpressionType))
    {
        try
        {
            string memberName = expression.Member.Name;
            PropertyChangedEventHandler handler = delegate(object sender, PropertyChangedEventArgs args)
                {
                    if (args.PropertyName == memberName)
                    {
                        OnValueChanged();
                    }
                };

            var owner = (INotifyPropertyChanged)GetValue(ownerExpression);
            owner.PropertyChanged += handler;

            handlers[handler] = new WeakReference(owner);
        }
        catch (Exception ex)
        {
            Console.WriteLine("ComputedValue failed to resolve INotifyPropertyChanged value for property {0} {1}", 
                                expression.Member, ex);
        }
    }
}

Retrieving the property value from the Lambda expression is done via expression compilation (see Listing 6). However, the expression is not able to be compiled directly as it is just a fragment. A Lambda expression is constructed by converting the specified expression into a UnaryExpression. When compiled, the resulting func is called and the result returned. In most cases the result is your viewmodel. Though, it does not have to be.

Listing 6. GetValue Method

object GetValue(Expression expression)
{
    UnaryExpression unaryExpression = Expression.Convert(expression, typeof(object));
    Expression<Func<object>> getterLambda = Expression.Lambda<Func<object>>(unaryExpression);
    Func<object> getter = getterLambda.Compile();

    return getter();
}

In .NET the target of an event may be ineligible for garbage collection while the event source remains alive. Therefore if we do not unsubscribe from owner objects’ PropertyChanged events, a memory leak may ensue. The ComputedValue class implements IDisposable and when disposed the ComputedValue instance unsubscribes from all change events.

The ComputedValue IDisposable implementation loops over the items in the handlers dictionary (see Listing 7). If KeyValuePair.Value property holds a WeakReference to the owner of the property, where the owner has not been disposed, the PropertyChanged event is unsubscribed using the handler stored as the KeyValuePair.Key property.

Listing 7. ComputedValue IDisposable Implementation

bool disposed;

protected virtual void Dispose(bool disposing)
{
    if (!disposed)
    {
        if (disposing)
        {
            foreach (KeyValuePair<PropertyChangedEventHandler, WeakReference> pair in handlers)
            {
                INotifyPropertyChanged target = pair.Value.Target as INotifyPropertyChanged;
                if (target != null)
                {
                    target.PropertyChanged -= pair.Key;
                }
            }

            handlers.Clear();
        }

        disposed = true;
    }
}

public void Dispose()
{
    Dispose(true);
}

The downloadable code contains a simple single window application that displays two TextBox controls. Each TextBox is bound to a viewmodel property of type ObservableValue. There is a TextBlock that is bound to a ComputedValue property, which combines the result of the two ObservableValue properties (see Figure 1). When the text in either of the TextBoxes is changed, the TextBlock containing the full name is updated.

Note how the LastName property is converted to upper case. This is performed despite the fact that the call is embedded within the Lambda expression of the ComputedValue.

Figure 1.Sample App.

Just to prove no funny business is taking place in the Window XAML, Listing 8 shows how the text properties are bound to the Value property of the ObservableValue properties and the ComputedValue property.

Listing 8. MainWindows.xaml (Excerpt)

<StackPanel Orientation="Horizontal" Margin="40">
    <StackPanel>
        <Label>First Name</Label>
        <TextBox Text="{Binding Path=FirstName.Value, UpdateSourceTrigger=PropertyChanged}" />
        <Label>Last Name</Label>
        <TextBox Text="{Binding Path=LastName.Value, UpdateSourceTrigger=PropertyChanged}" />
    </StackPanel>
    <StackPanel Margin="20,0">
        <Label>Full Name</Label>
        <TextBlock Text="{Binding Path=FullName.Value}" 
                    Style="{StaticResource FullNameStyle}" />
                
        <TextBlock>fullName = new ComputedValue&lt;string&gt;(<LineBreak />
            () => FirstName.Value + " " + ToUpper(LastName.Value));</TextBlock>
    </StackPanel>
</StackPanel>

The MainWindowViewModel code is shown in its entirety in Listing 9. The FirstName and LastName properties are combined in the ComputedValue, along with an arbitrary call to a ToUpper method.

Listing 9. MainWindowViewModel Class

class MainWindowViewModel
{
    readonly ObservableValue<string> firstName = new ObservableValue<string>("Alan");

    public ObservableValue<string> FirstName
    {
        get
        {
            return firstName;
        }
    }

    readonly ObservableValue<string> lastName = new ObservableValue<string>("Turing");

    public ObservableValue<string> LastName
    {
        get
        {
            return lastName;
        }
    }

    readonly ComputedValue<string> fullName;

    public ComputedValue<string> FullName
    {
        get
        {
            return fullName;
        }
    }

    public MainWindowViewModel()
    {
        fullName = new ComputedValue<string>(() => FirstName.Value + " " + ToUpper(LastName.Value));
    }

    string ToUpper(string s)
    {
        return s.ToUpper();
    }
}

Knockout JS has some elegant features that leverage the versatility of JavaScript and help to bridge the gap between XAML and HTML. In this article we looked at implementing field level change notification in WPF, and how a Lambda Expression can be used to specify a composite property that raises change notifications automatically whenever an associated property changes.

Vaughan.ObservablePrototype_2012_07_15.rar (250.68 kb)

Follow me on twitter



MetaGen: A project metadata generator for Visual Studio using T4

clock August 15, 2009 16:31 by author Daniel Vaughan

I am rather excited to share with you something that I have been working on in my spare time for the last couple of days. I have used T4 to build a metadata generator for your Silverlight and Desktop CLR projects. It can be used as a replacement for static reflection (expression trees), reflection (walking the stack), and various other means for deriving the name of a property, method, or field.

There has been much discussion’s around removing the property name string code smell from INotifyPropertyChanged implementations. Reflection is slow, and various techniques using reflection have been proposed, but have been criticized for contributing to decreased application performance. It now seems reasonable that a language extension for property change notification might be in order. But, as we don’t have that yet, I have created the next best thing: a generator.

A couple of days ago I began exploring T4 (Text Template Transformation Toolkit), and I’m loving it. T4, if you don’t already know is versatile templating system that allows you to generate classes, sql scripts etc. from within Visual Studio. If you have Visual Studio 2008, then you already have T4 ready to go! To find out more about T4 visit http://msdn.microsoft.com/en-us/library/bb126445.aspx

How to use it

To use MetaGen, simply include the attached MetaGen.tt file in your project. That’s it!

The MetaGen.tt has a number of customizable constants to prevent name collisions in your project.

/// <summary>
/// The modifier to use when outputting classes.
/// </summary>
const string generatedClassAccessModifier = "internal";

/// <summary>
/// The prefix to use for output class and interface names.
/// The combination of this and <see cref="generatedClassSuffix"/> provides
/// MetaGen with the ability to identify those classes etc.,
/// for which it should generated metadata, and to ignore MetaGen generated classes.
/// </summary>
const string generatedClassPrefix = "";

/// <summary>
/// The suffix to use for output class and interface names.
/// The combination of this and <see cref="generatedClassSuffix"/> provides
/// MetaGen with the ability to identify those classes etc.,
/// for which it should generated metadata, and to ignore MetaGen generated classes.
/// </summary>
const string generatedClassSuffix = "Metadata";

/// <summary>
/// The child namespace in which to place generated items.
/// If there is a class in MyNamespace namespace,
/// the metadata class will be generated
/// in the MyNamespace.[generatedClassSuffix] namespace.
/// This string can be null or empty, in which case a subnamesapce
/// will not be created, and generated output will reside
/// in the original classes namespace.
/// </summary>
const string generatedNamespace = "Metadata";

/// <summary>
/// The number of spaces to insert for a one step indent.
/// </summary>
const int tabSize = 4;

Template Implementation

The template consists of a procedural portion of code that retrieves the Visual Studio EnvDTE.DTE instance. This allows us to manipulate the Visual Studio automation object model, and to retrieve file, class, and method information and so on. Thanks go out to Oleg Sych for the T4 Toolbox which demonstrated how to retrieve the EnvDTE.DTE from the template hosting environment.

Once we object the relevant EnvDTE.Project we are able to process the EnvDTE.ProjectItems (files and directories in this case) as the following excerpt shows:

    IServiceProvider hostServiceProvider = (IServiceProvider)Host;
EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
EnvDTE.ProjectItem containingProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
Project project = containingProjectItem.ContainingProject;

/* Build the namespace representations, which contain class etc. */
Dictionary<string, NamespaceBuilder> namespaceBuilders = new Dictionary<string, NamespaceBuilder>();
foreach (ProjectItem projectItem in project.ProjectItems)
{
ProcessProjectItem(projectItem, namespaceBuilders);
}

We then recursively process EnvDTE.CodeElements and directories in order to create an object model representing the project.

 

public void ProcessProjectItem(ProjectItem projectItem, Dictionary<string, NamespaceBuilder> namespaceBuilders)
{
FileCodeModel fileCodeModel = projectItem.FileCodeModel;

if (fileCodeModel != null)
{
foreach (CodeElement codeElement in fileCodeModel.CodeElements)
{
WalkElements(codeElement, null, null, namespaceBuilders);
}
}

if (projectItem.ProjectItems != null)
{
foreach (ProjectItem childItem in projectItem.ProjectItems)
{
ProcessProjectItem(childItem, namespaceBuilders);
}
}

}

int indent;

public void WalkElements(CodeElement codeElement, CodeElement parent,
BuilderBase parentContainer, Dictionary<string, NamespaceBuilder> namespaceBuilders)
{
indent++;
CodeElements codeElements;

if (parentContainer == null)
{
NamespaceBuilder builder;
string name = "global";
if (!namespaceBuilders.TryGetValue(name, out builder))
{
builder = new NamespaceBuilder(name, null, 0);
namespaceBuilders[name] = builder;
}
parentContainer = builder;
}

switch(codeElement.Kind)
{
/* Handle namespaces */
case vsCMElement.vsCMElementNamespace:
{
CodeNamespace codeNamespace = (CodeNamespace)codeElement;
string name = codeNamespace.FullName;
if (!string.IsNullOrEmpty(generatedNamespace) && name.EndsWith(generatedNamespace))
{
break;
}

NamespaceBuilder builder;

if (!namespaceBuilders.TryGetValue(name, out builder))
{
builder = new NamespaceBuilder(name, null, 0);
namespaceBuilders[name] = builder;
}

codeElements = codeNamespace.Members;
foreach (CodeElement element in codeElements)
{
WalkElements(element, codeElement, builder, namespaceBuilders);
}
break;
}
/* Process classes */
case vsCMElement.vsCMElementClass:
{
CodeClass codeClass = (CodeClass)codeElement;
string name = codeClass.Name;
if (string.IsNullOrEmpty(generatedNamespace)
&& name.StartsWith(generatedClassPrefix)
&& name.EndsWith(generatedClassSuffix))
{
break;
}

List<string> comments = new List<string>();
comments.Add(string.Format("/// <summary>Metadata for class <see cref=\"{0}\"/></summary>", codeClass.FullName));

BuilderBase builder;
if (!parentContainer.Children.TryGetValue(name, out builder))
{
builder = new ClassBuilder(name, comments, indent);
parentContainer.Children[name] = builder;
}
codeElements = codeClass.Members;
if (codeElements != null)
{
foreach (CodeElement ce in codeElements)
{
WalkElements(ce, codeElement, builder, namespaceBuilders);
}
}
break;
}
/* Process interfaces. */
case vsCMElement.vsCMElementInterface:
{
CodeInterface codeInterface = (CodeInterface)codeElement;
string name = codeInterface.Name;
if (name.StartsWith(generatedClassPrefix) && name.EndsWith(generatedClassSuffix))
{
break;
}
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeInterface.FullName);
comments.Add(string.Format("/// <summary>Metadata for interface <see cref=\"{0}\"/></summary>", commentName));
InterfaceBuilder builder = new InterfaceBuilder(name, comments, indent);
parentContainer.AddChild(builder);

codeElements = codeInterface.Members;
if (codeElements != null)
{
foreach (CodeElement ce in codeElements)
{
WalkElements(ce, codeElement, builder, namespaceBuilders);
}
}
break;
}
/* Process methods */
case vsCMElement.vsCMElementFunction:
{
CodeFunction codeFunction = (CodeFunction)codeElement;
if (codeFunction.Name == parentContainer.Name
|| codeFunction.Name == "ToString"
|| codeFunction.Name == "Equals"
|| codeFunction.Name == "GetHashCode"
|| codeFunction.Name == "GetType"
|| codeFunction.Name == "MemberwiseClone"
|| codeFunction.Name == "ReferenceEquals")
{
break;
}

string name = codeFunction.Name.Replace('.', '_');
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeFunction.FullName);
comments.Add(string.Format("/// <summary>Name of method <see cref=\"{0}\"/></summary>", commentName));
MemberBuilder builder = new MemberBuilder(name, comments, indent);
parentContainer.AddChild(builder);
break;
}
/* Process properties. */
case vsCMElement.vsCMElementProperty:
{
CodeProperty codeProperty = (CodeProperty)codeElement;

string name = codeProperty.Name.Replace('.', '_');
if (name != "this")
{
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeProperty.FullName);
comments.Add(string.Format("/// <summary>Name of property <see cref=\"{0}\"/></summary>", commentName));
MemberBuilder builder = new MemberBuilder(name, comments, indent);
parentContainer.AddChild(builder);
}
break;
}
/* Process fields. */
case vsCMElement.vsCMElementVariable:
{
CodeVariable codeVariable = (CodeVariable)codeElement;
string name = codeVariable.Name;
List<string> comments = new List<string>();
string commentName = FormatTypeNameForComment(codeVariable.FullName);
comments.Add(string.Format("/// <summary>Name of field <see cref=\"{0}\"/></summary>", commentName));
MemberBuilder builder = new MemberBuilder(name, comments, indent);
parentContainer.AddChild(builder);
break;
}
}
indent--;
}

Once this is complete we output our namespace representations to the resulting MetaGen.cs file like so:

/* Finally, write them to the output. */
foreach (object item in namespaceBuilders.Values)
{
WriteLine(item.ToString());
}

What results is a file containing various namespace blocks that include static classes representing our non-metadata classes and interfaces with the project. Property names, method names, and field names are represented as constants. Inner classes are represented as nested static classes.

I have included with this post the MetaGen.tt template file, and also a demo WPF application. If you have any suggestions such as ideas for other metadata information etc., please let me know.

MetaGen.tt (14.92 kb)  (Just the T4 template)

MetaGen.zip (60.08 kb)  (The T4 template and an demo application)

Update: I've published a more recent article on CodeProject with a newer enriched template and examples.

Please note: If you are after the latest and greatest version of this template, please procure it from the Calcium SDK (in the core project).



Property Change Notification using a Weak Referencing Strategy

clock August 2, 2009 22:03 by author Daniel Vaughan

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.

class MockNotifyPropertyChanged : INotifyPropertyChanged, INotifyPropertyChanging
{
    readonly PropertyChangeNotifier notifier;

    #region INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            notifier.PropertyChanged += value;
        }
        remove
        {
            notifier.PropertyChanged -= value;
        }
    }
    #endregion

    #region INotifyPropertyChanging Implementation
    public event PropertyChangingEventHandler PropertyChanging
    {
        add
        {
            notifier.PropertyChanging += value;
        }
        remove
        {
            notifier.PropertyChanging -= value;
        }
    }
    #endregion

    public MockNotifyPropertyChanged()
    {
        notifier = new PropertyChangeNotifier(this);
    }

    int int1;

    public int TestPropertyAssigned
    {
        get
        {
            return int1;
        }
        set
        {
            notifier.Assign(
                "TestPropertyAssigned", ref int1, value);
        }
    }

    string string1;

    public string TestPropertyAssignedLambda
    {
        get
        {
            return string1;
        }
        set
        {
            notifier.Assign(
                (MockNotifyPropertyChanged mock) => mock.TestPropertyAssignedLambda, 
                ref string1, value);
        }
    }    
}

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.

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

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

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

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

    #region event PropertyChanged

#if !SILVERLIGHT
    [field: NonSerialized]
#endif
    event PropertyChangedEventHandler propertyChanged;

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

    /// <summary>
    /// Raises the <see cref="E:PropertyChanged"/> event.
    /// If the owner has been GC'd then the event will not be raised.
    /// </summary>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> 
    /// instance containing the event data.</param>
    void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var owner = ownerWeakReference.Target;
        if (owner != null && propertyChanged != null)
        {
            propertyChanged(owner, e);
        }
    }

    #endregion

    /// <summary>
    /// Assigns the specified newValue to the specified property
    /// and then notifies listeners that the property has changed.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="propertyName">Name of the property. Can not be null.</param>
    /// <param name="property">A reference to the property that is to be assigned.</param>
    /// <param name="newValue">The value to assign the property.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void Assign<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
        ValidatePropertyName(propertyName);

        AssignWithNotificationAux(propertyName, ref property, newValue);
    }

    /// <summary>
    /// Slow. Not recommended.
    /// Assigns the specified newValue to the specified property
    /// and then notifies listeners that the property has changed.
    /// Assignment nor notification will occur if the specified
    /// property and newValue are equal. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="expression">The expression that is used to derive the property name.
    /// Should not be <code>null</code>.</param>
    /// <param name="property">A reference to the property that is to be assigned.</param>
    /// <param name="newValue">The value to assign the property.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void Assign<T, TProperty>(
        Expression<Func<T, TProperty>> expression, ref TProperty property, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        string propertyName = GetPropertyName(expression);
        AssignWithNotificationAux(propertyName, ref property, newValue);
    }

    void AssignWithNotificationAux<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        /* Boxing may occur here. We should consider 
         * providing some overloads for primitives. */
        if (Equals(property, newValue)) 
        {
            return;
        }

        if (useExtendedEventArgs)
        {
            var args = new PropertyChangingEventArgs<TProperty>(propertyName, property, newValue);

            OnPropertyChanging(args);
            if (args.Cancelled)
            {
                return;
            }

            var oldValue = property;
            property = newValue;
            OnPropertyChanged(new PropertyChangedEventArgs<TProperty>(
                propertyName, oldValue, newValue));
        }
        else
        {
            var args = RetrieveOrCreatePropertyChangingEventArgs(propertyName);
            OnPropertyChanging(args);

            var changedArgs = RetrieveOrCreatePropertyChangedEventArgs(propertyName);
            OnPropertyChanged(changedArgs);
        }
    }

    readonly Dictionary<string, string> expressions = new Dictionary<string, string>();

    /// <summary>
    /// Notifies listeners that the specified property has changed.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="propertyName">Name of the property. Can not be null.</param>
    /// <param name="oldValue">The old value before the change occured.</param>
    /// <param name="newValue">The new value after the change occured.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void NotifyChanged<TProperty>(
        string propertyName, TProperty oldValue, TProperty newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }
        ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
        ValidatePropertyName(propertyName);

        if (ReferenceEquals(oldValue, newValue))
        {
            return;
        }

        var args = useExtendedEventArgs
            ? new PropertyChangedEventArgs<TProperty>(propertyName, oldValue, newValue)
            : RetrieveOrCreatePropertyChangedEventArgs(propertyName);

        OnPropertyChanged(args);
    }

    /// <summary>
    /// Slow. Not recommended.
    /// Notifies listeners that the property has changed.
    /// Notification will occur if the specified
    /// property and newValue are equal. 
    /// </summary>
    /// <param name="expression">The expression that is used to derive the property name.
    /// Should not be <code>null</code>.</param>
    /// <param name="oldValue">The old value of the property before it was changed.</param>
    /// <param name="newValue">The new value of the property after it was changed.</param>
    /// <exception cref="ArgumentNullException">
    /// Occurs if the specified propertyName is <code>null</code>.</exception>
    /// <exception cref="ArgumentException">
    /// Occurs if the specified propertyName is an empty string.</exception>
    public void NotifyChanged<T, TResult>(
        Expression<Func<T, TResult>> expression, TResult oldValue, TResult newValue)
    {
        if (OwnerDisposed)
        {
            return;
        }

        ArgumentValidator.AssertNotNull(expression, "expression");

        string name = GetPropertyName(expression);
        NotifyChanged(name, oldValue, newValue);
    }
    
    static MemberInfo GetMemberInfo<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        var member = expression.Body as MemberExpression;
        if (member != null)
        {
            return member.Member;
        }

        /* TODO: Make localizable resource. */
        throw new ArgumentException("MemberExpression expected.", "expression");
    }

    #region INotifyPropertyChanging Implementation
#if !SILVERLIGHT
    [field: NonSerialized]
#endif
    event PropertyChangingEventHandler propertyChanging;

    public event PropertyChangingEventHandler PropertyChanging
    {
        add
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanging += value;
        }
        remove
        {
            if (OwnerDisposed)
            {
                return;
            }
            propertyChanging -= value;
        }
    }

    /// <summary>
    /// Raises the <see cref="E:PropertyChanging"/> event.
    /// If the owner has been GC'd then the event will not be raised.
    /// </summary>
    /// <param name="e">The <see cref="System.ComponentModel.PropertyChangingEventArgs"/> 
    /// instance containing the event data.</param>
    void OnPropertyChanging(PropertyChangingEventArgs e)
    {
        var owner = ownerWeakReference.Target;
        if (owner != null && propertyChanging != null)
        {
            propertyChanging(owner, e);
        }
    }
    #endregion

#if SILVERLIGHT
    readonly object expressionsLock = new object();

    string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        string name;
        lock (expressionsLock)
        {
            if (!expressions.TryGetValue(expression.ToString(), out name))
            {
                if (!expressions.TryGetValue(expression.ToString(), out name))
                {
                    var memberInfo = GetMemberInfo(expression);
                    if (memberInfo == null)
                    {
                        /* TODO: Make localizable resource. */
                        throw new InvalidOperationException("MemberInfo not found.");
                    }
                    name = memberInfo.Name;
                    expressions.Add(expression.ToString(), name);
                }
            }
        }

        return name;
    }
#else
    readonly ReaderWriterLockSlim expressionsLock = new ReaderWriterLockSlim();

    string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> expression)
    {
        string name;
        expressionsLock.EnterUpgradeableReadLock();
        try
        {
            if (!expressions.TryGetValue(expression.ToString(), out name))
            {
                expressionsLock.EnterWriteLock();
                try
                {
                    if (!expressions.TryGetValue(expression.ToString(), out name))
                    {
                        var memberInfo = GetMemberInfo(expression);
                        if (memberInfo == null)
                        {
                            /* TODO: Make localizable resource. */
                            throw new InvalidOperationException("MemberInfo not found.");
                        }
                        name = memberInfo.Name;
                        expressions.Add(expression.ToString(), name);
                    }
                }
                finally
                {
                    expressionsLock.ExitWriteLock();
                }
            }
        }
        finally
        {
            expressionsLock.ExitUpgradeableReadLock();
        }
        return name;
    }
#endif

    bool cleanupOccured;

    bool OwnerDisposed
    {
        get
        {
            /* We slightly improve performance here 
             * by avoiding multiple Owner property calls 
             * after the Owner has been disposed. */
            if (cleanupOccured)
            {
                return true;
            }
            var owner = Owner;
            if (owner != null)
            {
                return false;
            }
            cleanupOccured = true;
            var changedSubscribers = propertyChanged.GetInvocationList();
            foreach (var subscriber in changedSubscribers)
            {
                propertyChanged -= (PropertyChangedEventHandler)subscriber;
            }
            var changingSubscribers = propertyChanging.GetInvocationList();
            foreach (var subscriber in changingSubscribers)
            {
                propertyChanging -= (PropertyChangingEventHandler)subscriber;
            }

            /* Events should be null at this point. Nevertheless... */
            propertyChanged = null;
            propertyChanging = null;
            propertyChangedEventArgsCache.Clear();
            propertyChangingEventArgsCache.Clear();

            return true;
        }
    }

    [Conditional("DEBUG")]
    void ValidatePropertyName(string propertyName)
    {
#if !SILVERLIGHT
        var propertyDescriptor = TypeDescriptor.GetProperties(Owner)[propertyName];
        if (propertyDescriptor == null)
        {
            /* TODO: Make localizable resource. */
            throw new Exception(string.Format(
                "The property '{0}' does not exist.", propertyName));
        }
#endif
    }

    bool useExtendedEventArgs;
    readonly Dictionary<string, PropertyChangedEventArgs> propertyChangedEventArgsCache = new Dictionary<string, PropertyChangedEventArgs>();
    readonly Dictionary<string, PropertyChangingEventArgs> propertyChangingEventArgsCache = new Dictionary<string, PropertyChangingEventArgs>();

#if SILVERLIGHT
    readonly object propertyChangingEventArgsCacheLock = new object();

    PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(string propertyName)
    {
        var result = RetrieveOrCreateEventArgs(
            propertyName, 
            propertyChangingEventArgsCacheLock, 
            propertyChangingEventArgsCache, 
            x => new PropertyChangingEventArgs(x));

        return result;
    }

    readonly object propertyChangedEventArgsCacheLock = new object();

    PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(string propertyName)
    {
        var result = RetrieveOrCreateEventArgs(
            propertyName,
            propertyChangedEventArgsCacheLock,
            propertyChangedEventArgsCache,
            x => new PropertyChangedEventArgs(x));

        return result;
    }

    static TArgs RetrieveOrCreateEventArgs<TArgs>(
        string propertyName, object cacheLock, Dictionary<string, TArgs> argsCache, 
        Func<string, TArgs> createFunc)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        TArgs result;

        lock (cacheLock)
        {
            if (argsCache.TryGetValue(propertyName, out result))
            {
                return result;
            }

            result = createFunc(propertyName);
            argsCache[propertyName] = result;
        }
        return result;
    }
#else
    readonly ReaderWriterLockSlim propertyChangedEventArgsCacheLock = new ReaderWriterLockSlim();
    
    PropertyChangedEventArgs RetrieveOrCreatePropertyChangedEventArgs(string propertyName)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        var result = RetrieveOrCreateArgs(
            propertyName,
            propertyChangedEventArgsCache,
            propertyChangedEventArgsCacheLock,
            x => new PropertyChangedEventArgs(x));

        return result;
    }

    readonly ReaderWriterLockSlim propertyChangingEventArgsCacheLock = new ReaderWriterLockSlim();

    static TArgs RetrieveOrCreateArgs<TArgs>(string propertyName, Dictionary<string, TArgs> argsCache,
        ReaderWriterLockSlim lockSlim, Func<string, TArgs> createFunc)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        TArgs result;
        lockSlim.EnterUpgradeableReadLock();
        try
        {
            if (argsCache.TryGetValue(propertyName, out result))
            {
                return result;
            }
            lockSlim.EnterWriteLock();
            try
            {
                if (argsCache.TryGetValue(propertyName, out result))
                {
                    return result;
                }
                result = createFunc(propertyName);
                argsCache[propertyName] = result;
                return result;
            }
            finally
            {
                lockSlim.ExitWriteLock();
            }
        }
        finally
        {
            lockSlim.ExitUpgradeableReadLock();
        }
    }

    PropertyChangingEventArgs RetrieveOrCreatePropertyChangingEventArgs(string propertyName)
    {
        ArgumentValidator.AssertNotNull(propertyName, "propertyName");
        var result = RetrieveOrCreateArgs(
            propertyName,
            propertyChangingEventArgsCache,
            propertyChangingEventArgsCacheLock,
            x => new PropertyChangingEventArgs(x));

        return result;
    }
#endif

}

 

I’ve extended the PropertyChangedEventArgs and the PropertyChangingEventArgs to include before and after values. The following excerpt shows the PropertyChangedEventArgs.

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



Order the Book

Ready to take your Windows Phone development skills to the next level? My book is the first comprehensive, start-to-finish developer's guide to Microsoft's Windows Phone 8. In it I teach through complete sample apps that illuminate each key concept with fully explained code and real-world context. Windows Phone 8 Unleashed

Windows Phone Experts Windows Phone Experts
LinkedIn Group

 

Bio

Daniel VaughanDaniel Vaughan is co-founder and president 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 technologiesin particular WPF, WinRT, and Windows Phone. 

Daniel is a four-time Microsoft MVP for Client Application Development, with experience across a wide range of industries including finance, e-commerce, and digital media. 
Daniel is the author of Windows Phone 7.5 Unleashed and Windows Phone 8 Unleashed, published by SAMS.

Daniel is a Silverlight and WPF Insider, a member of the WPF Disciples, and a member of the Microsoft Developer Guidance Advisory Council.
Daniel also sits on the advisory board of PebbleAge, a Swiss Financial Software company.

While originally from Australia and the UK, Daniel is currently based in Zurich Switzerland. 

Daniel is the developer behind several acclaimed Windows Phone apps including Intellicam and Splashbox; and is the creator of a number of popular open-source projects including the Calcium and Clog.

Daniel also manages the Windows Phone Experts group on LinkedIn; a group that has over 3000 independent developers, Microsoft employees, and Windows Phone enthusiasts.


E-mail Send mail

 

Microsoft MVP logo Disciple
WPF and Silverlight Insiders
 

 

 

Sign in