Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Chained Property Observer

0.00/5 (No votes)
8 Mar 2011 4  
Set of utility classes to observe a chain of INotifyPropertyChanged objects.

Introduction

I have mixed feelings in writing this article; half of me thinks it is a really cool idea, and the other half tends to agree with my old team leader Fredrik Bornander, who when I explained what I wanted to do, simply uttered the single word "TrainWreck", and then later sent me a link to this Wikipedia article:

Now there is a reason young Fredrik used to be my team leader, and in general, he is normally pretty right, but I just felt there was something of interest/use with the code I showed him, so I plowed on, and wrote the code that I am demonstrating in this article. Sorry Fredrik, no disrespect intended.

Like I say, I may be presenting something here that some folk don't sit well with, but I think the code attached to this article does have some merit and demonstrates some good .NET techniques even if you do not decide to use the overall utility classes that I present in this article.

Now that you have read this far, I suppose I should tell you what the article is all about, so you can decide whether to read on or not...so here we go.

Some of you may be aware of an interface that is quite common place in pretty much most UI programming in Windows, it is the INotifyPropertyChanged interface which allows objects that implement this interface to raise change notification events that other objects/framework subsystems (such as the binding subsystem) can listen to.

What this article will show is how we can create a INotifyPropertyChanged observer that is able to monitor any object within a chain of INotifyPropertyChanged objects, which may sound strange at first, but think about something like this scenario:

We need a database that allows the storage of people, where each person has an address, and an address is linked through to some geo-location data. That sounds pretty valid to me, and a good normalized database will be littered with similar scenarios.

Now, common sense and database normalization laws would dictate that we would end up with something that looked like this:

So we would translate that into an object graph, something like this:

public class Person : INotifyPropertyChanged
{
   private Address address;

   public Address Address
   {
     ...
   }

   ...
   ...
}

public class Address : INotifyPropertyChanged
{
   private GeoLocation geoLocaation;

   public GeoLocation GeoLocaation
   {
     ...
   }

   ...
   ...
}

public class GeoLocation : INotifyPropertyChanged
{
   ...
   ...
}

Which is the sort of graph we would get if we were to manually code the object graph, or what would we get if we were to use something like LINQ to SQL, or LINQ to Entities?

One point about this is that, to my mind, the database is normalized correctly, we are storing things where and how we should, which again to my mind makes the object graph hierarchy correct, but as you can see, there is definitely a chain of related objects there.

So now that we have an object heirachy like that (or any other example you can think of), it (using my example) would not be too much of an outlandish a request to say that whenever a Person's details change, we should do some action.

To clarify what I mean by "when a Person's data changes", what I really mean is if the Person's data changes, or the Person's Address changes, or the Person's Address' geo-location data changes, we should do something, that is what I really mean.

Imagine we want to send a new Invoice when any part of the Person's data changes, when we are using a Person object as part of a larger process. Now using conventional methods, I would have to hook up three listeners (one for Person, one for the Person's Address, and one for the Address' geo-location) in some top level class which holds an instance of a Person object, which in itself is not that bad, but what about if a new Address object is assigned to that Person object at some point, that we could not predict?

We would effectively have an INPC event handler (for the old Address object of the Person object) pointing to an out of date object that we really should have unhooked, but when would we do that? How would we know? We would have to listen to changes on the Person object where the property being changed is "Address", and unhook the old address INPC listener there, which would mean we would then need to re-hook a new INPC event handler for the Person's Address.

OK, weak events can help here, so we do not necessarily have to worry about unhooking, but we would still need to re-hook to the new Person's Address object... as you can see, there is quite a bit of house keeping that needs to be done. My thoughts on this were, why should I have to do that house keeping? Surely, I could write some smart property observer that could do all that jazz for me.

This article demonstrates a group of classes that makes it possible to more easily monitor a change anywhere in a chain of objects. In fact, you can choose what parts of the chain you want to monitor changes on. So if this sounds like it could be of use to you, read on, otherwise no worries, hope to see you in the next article.

Similar Ideas

Now although as far as I am aware there is no existing solution out there that does the same as the code I will present in this article, there is one other project that this article certainly took some inspiration from, which is the excellent lambda bindings article from my fellow WPF Disciple Philip Sumi, who had the idea to allow two lambda expressions to be passed to a small class that would then be bound to each other either OneWay, TwoWay, and it also supports ValueConverters.

Although there are certain similarities in that we are both dealing with lambda expressions (as you will see below), what we are trying to achieve is quite different functionality; my code is all about chained INPC callbacks, whilst Philip's is all about binding one lambda expression to another.

Table of Contents

This is what will be covered in this article:

How the Code Solves the Problem

The main class that deals with most of the work is called "ChainPropertyObserver". Here is how it works, in words:

  1. We create an initial chain of nodes that observe the object in the chain which is supplied by the lambda expression. These are represented internally as a List<ChainNode>. The List<ChainNode> is obtained by obtaining the string values for the property names in the lambda expression for the properties visited, and then a little bit of Reflection is used to obtain the actual objects which we store against the actual ChainNode instances. We also hook up an overall listener that is called back when any node in the chain changes.
  2. We also accept n-many number of specific callback handlers to watch specific property changes for within the chain. These are represented internally as a List<PropertyNode>. The List<PropertyNode> is obtained by obtaining string values for the property names in the lambda expression for the properties visited, and then a little bit of Reflection is used to obtain the actual objects which we store against the actual ChainNode instances.
  3. When the specific callback handlers are added, we lookup the ChainNode that has the same object type and property name as the callback, and assign the specific callback delegate that was registered for a specific property change to the ChainNode that matches. Basically, we only obtain one set of nodes that fire the callback delegates, and that is the List<ChainNode>, so we must find the correct one and assign it the correct callback delegate.
  4. When a node value is detected as being changed, we obtain its position in the chain, remove any nodes after and including it from the overall List<ChainNode>, and then recreate the rest of the changed chain using the new object graph using a tiny bit of Reflection. We also re-hook up any specific property change to the ChainNode, as described in step 3.

In essence, what the ChainPropertyObserver holds internally is one set of nodes (known as ChainNode) to represent the original lambda that was passed into the CreateChain(..) method, and another list of nodes to represent the lambdas that were passed in using the RegisterChainLinkHandler(..) method.

Let's consider a quick diagram or two.

Chain Creation Nodes

When we first start up and create a chain using the ChainPropertyObserver.CreateChain(..) method, we would end up with (depending on the lambda expression that was passed into the method CreateChain(..) method) a List<ChainNode> that is held inside the ChainPropertyObserver. Where each internal ChainNode holds a reference to the property of the original object tree as specified by the lambda expression passed in.

So for example, if we passed in a lambda of Person.Address.GeoLocation, we would end with an internal List<ChainNode> to represent this lambda expression that would contain three ChainNodes configured as:

  1. ChainNode with an object reference to the Person object property of whichever object held the Person instance
  2. ChainNode with an object reference to the Address object property of the Person object in the previous ChainNode
  3. ChainNode with an object reference to the GeoLocation object property of the Address object in the previous ChainNode

INPC Callback Nodes

When we register specific callbacks using the ChainPropertyObserver.RegisterChainLinkHandler(..) method, we pretty much follow the same process as before, except this time, we end up making a List<PropertyNode> when we parse the lambda expression passed to the ChainPropertyObserver.RegisterChainLinkHandler(..) method.

So for example, if we passed in a lambda of Person.Address.GeoLocation and we supplied the following when we called the ChainPropertyObserver.RegisterChainLinkHandler(..) method twice, we would end up with the following List<PropertyNode> created internally.

For the RegisterChainHandler(() => Person.Address)

  1. PropertyNode with an object reference to the Person object property of whichever object held the Person instance
  2. PropertyNode with an object reference to the Address object property of the Person object in the previous PropertyNode

For the RegisterChainHandler(() => Person.Address.GeoLocation)

  1. PropertyNode with an object reference to the Person object property of whichever object held the Person instance
  2. PropertyNode with an object reference to the Address object property of the Person object in the previous PropertyNode
  3. PropertyNode with an object reference to the GeoLocation object property of the Address object in the previous PropertyNode

So I hope that makes some sort of sense. Anyway, that is how it all works, in words, but I think you lot would probably like to see some code now, right? Let's crack on and have a look at that.

Parsing the Original Chain

As we now know, there is a method that is available on the ChainPropertyObserver which must be used to set an initial chain, where the usage of this method (from one of the demos) is like this:

MyPerson = new Person();
MyPerson.Age = 12;
MyPerson.Name = "sacha";
MyPerson.Address = new Address()
{
    Addressline1 = "21 maple street",
    Addressline2 = "Hanover",
    GeoLocation = new GeoLocation { Longitude=0.5, Latitude=0.5 }
};

//create chainPropertyObserver
chainPropertyObserver = new ChainPropertyObserver();
chainPropertyObserver.CreateChain(() => MyPerson.Address.GeoLocation, Changed);

...
...
private void Changed(string propertyThatChanged)
{
    lastChanges.Add(propertyThatChanged);
}

There are several things of note there, namely that we have a property called MyPerson of type Person which is whatever test class this is, and we supply a lambda expression to the ChainPropertyObserver.CreateChain(..) taking an overall chain and an overall change handler delegate. What happens after that?

Well, let's start at the top, which is the ChainPropertyObserver.CreateChain(..)method:

public class ChainPropertyObserver : ExpressionVisitor
{
    public void CreateChain<TSource>(Expression<Func<TSource>> source, 
        Action<string> overallChangedCallback)
    {
        this.overallChangedCallback = 
        new WeakDelegateReference(overallChangedCallback);
        Visit(source);
        CreateNodes();
        isInitialised = true;
    }
}

It can be seen that the ChainPropertyObserver inherits from the System.Linq.Expressions.ExpressionVisitor class. As such, it allows us to Visit(..) an expression tree, which in our case is the Expression<Func<TSource>> source supplied to the ChainPropertyObserver.CreateChain(..) method. It can be seen above in the ChainPropertyObserver.CreateChain(..) method that we do indeed call the base class (System.Linq.Expressions.ExpressionVisitor class) Visit(..) method. So let's have a look at what happens when we Visit(..) some expressions.

It is pretty simple; we simply need to provide overrides for the expressions we wish to visit. Here they are:

/// <summary>
/// Visits a member within a Expression tree
/// So for example if we had a console app called
/// "ExpressionTest.Program" that had a public property like
/// public Person MyPerson { get; set; }
// and we then passed in a overall expression to this
/// class of chainPropertyObserver.CreateChain(() => MyPerson.Address.GeoLocation)
/// 
/// This method would visit the following members in this order : 
/// 1. GeoLocation
/// 2. Address
/// 3. MyPerson
/// </summary>
protected override Expression VisitMember(MemberExpression node)
{
    if (!isInitialised)
    {
        membersSeen.Insert(0, node.Member.Name);
    }
    else
    {
        propMemberSeen.Insert(0, node.Member.Name);
    }
    return base.VisitMember(node);
}

/// <summary>
/// Visits a constant within a Expression tree
/// So for example if we had a console app called
/// "ExpressionTest.Program" that had a public property like
/// public Person MyPerson { get; set; }
/// and we then passed in a overall expression to this
/// class of chainPropertyObserver.CreateChain(() => MyPerson.Address.GeoLocaation)
/// 
/// This method would visit a constant of the ExpressionTest.Program type object
/// </summary>
protected override Expression VisitConstant(ConstantExpression node)
{
    if (!isInitialised)
    {
        wrRoot = new WeakReference(node.Value);
    }
    else
    {
        wrPropRoot = new WeakReference(node.Value);
    }
    return base.VisitConstant(node);
}

Visit MemberExpression

It can be seen that we store two things when we visit a MemberExpression. We maintain a list of visited member names which are really just string names of the properties we have seen in the expression tree. We maintain two lists as we use these same visit methods to create ChainNode and PropertyNode, so we just store things in two internal lists, memberSeen for ChainNodes and propMemberSeen for PropertyNodes.

Visit ConstantExpression

It can be seen that we store two things when we visit a ConstantExpression. We maintain two objects which are really just the original objects that hold the source objects we have seen in the expression tree. We maintain two objects as we use these same visit methods to create ChainNode and PropertyNode, so we just store things in two internal objects, wrRoot for ChainNodes and wrPropRoot for PropertyNodes.

After we have visited the entire expression tree, we would end up with something like this being stored in the ChainPropertyObserver:

This assumes we are in one of the demo apps; that is why it is pointing to a WeakReference wrapped object that is the WPF app.

The last thing that happens is that an internal list of ChainNode (which inherits from NodeBase) is created using a bit of Reflection, where we make use of the wrRoot WeakReference and the membersSeen List<string>. These two bits of information are enough to allow us to fully reflect out an actual chain of objects, which we store in a List<ChainNode>, which internally also uses WeakReferences to wrap the object that the node represents.

Here is how the CreateNodes method works:

private void CreateNodes()
{
    ChainNode parent = null;
    bool chainBroken = false;
    for (int i = 0; i < membersSeen.Count; i++)
    {
        parent = (ChainNode)CreateNode(parent, membersSeen[i], 
                            NodeType.ChainNode, out chainBroken);
        //only create new ChainNodes as long as the chain is entact,
        //and has no null object references
        if (!chainBroken)
        {
            nodes.Add(parent);
            parent.DependencyChanged += Parent_DependencyChanged;
        }
        else
            break;
    }
}

private object CreateNode(NodeBase parent, string propNameForNewChild, 
               NodeType nodeType, out bool chainBroken)
{
    object nodeValue;
    if (parent == null)
    {
        object activeRoot = null;
        if (wrRoot.IsAlive)
        {
            activeRoot = wrRoot.Target;
        }

        if (activeRoot == null)
            throw new InvalidOperationException("Root has been garbage collected");

        nodeValue = activeRoot.GetType().GetProperty(
        propNameForNewChild).GetValue(activeRoot, null);
        if (nodeValue== null)
        {
            chainBroken=true;
            return null;
        }
    }
    else
    {
        nodeValue = parent.NodeValue.GetType().GetProperty(propNameForNewChild)
        .GetValue(parent.NodeValue, null);
        if (nodeValue == null)
        {
            chainBroken = true;
            return null;
        }
    }

    if (!(nodeValue is INotifyPropertyChanged))
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("ChainedObserver is only able to work with objects " +
              "that implement the INotifyPropertyChanged interface");
        sb.AppendLine("All objects in a chain MUST implement " + 
                      "the INotifyPropertyChanged interface");
        throw new InvalidOperationException(sb.ToString());
    }

    NodeBase node = null;
    switch (nodeType)
    {
        case NodeType.ChainNode:
            node = new ChainNode((ChainNode)parent, nodeValue, propNameForNewChild);
            break;
        case NodeType.PropertyNode:
            node = new PropertyNode((PropertyNode)parent, 
                       nodeValue, propNameForNewChild);
            break;
    }
    chainBroken = false;
    return node;
}

Some of you may notice that the CreateNode(..) method also has the capability to create PropertyNodes; we will get to that shortly.

Just to complete the picture, this is what a ChainNode looks like along with the other node types it inherits from:

using System;
using System.ComponentModel;

using ChainedObserver.Weak;
using ChainedObserver.WeakEvents;

namespace ChainedObserver
{
    /// <summary>
    /// Represents a node in the original Lambda expression that was
    /// passed to the <c>ChainPropertyObserver</c>. This class also
    /// provides an overall DependencyChanged weak event 
    /// (using Daniel Grunwalds<c>WeakEvent</c>) that is raised whenever
    /// the property that this <c>ChainNode</c> is monitoring changes.
    /// Basically this node hooks a <c>NotifyPropertyChanged</c>
    /// listener to a source object to know when a overall dependency changes.
    /// When the dependency changes the DependencyChanged is raied which 
    /// allows an overall callback delegate to be called when any node in the
    /// chain within the original Lambda expression changes. There is also
    /// a callback here may run a specific callback handler that was
    /// registered within the <c>ChainPropertyObserver</c>. This callback
    /// may or may not be null for any given <c>ChainNode</c>, it depends
    /// on what property this <c>ChainNode</c> is monitoring from the original
    /// Lambda expression, as to whether there will be an active specific
    /// callback assigned for a given <c>ChainNode</c>
    /// 
    /// When Dispose() is called this class will unhook
    /// any <c>NotifyPropertyChanged</c>
    /// listener that it has previously subscribed to
    /// </summary>
    public class ChainNode : GenericNodeBase<ChainNode>, IDisposable
    {
        private WeakEventHandler<PropertyChangedEventArgs> inpcHandler;

        public ChainNode(ChainNode parent, object value, String name) : 
                         base(parent,value,name)
        {
            inpcHandler = 
              new WeakEventHandler<PropertyChangedEventArgs>(PropertyChanged);
            ((INotifyPropertyChanged)value).PropertyChanged += inpcHandler.Handler;
        }

        public WeakDelegateReference PropertyChangedCallback { get; set; }

        private void PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //raise overall dependencyChangedEvent
            dependencyChangedEvent.Raise(this, new ChainNodeEventArgs(this));

            //now callback specific handler for this Node type/name
            if (PropertyChangedCallback != null)
            {
                Action callback = (Action)PropertyChangedCallback.Target;
                if (callback != null)
                {
                    callback();
                }
            }
        }

        private readonly WeakEvent<EventHandler<ChainNodeEventArgs>> dependencyChangedEvent =
            new WeakEvent<EventHandler<ChainNodeEventArgs>>();

        public event EventHandler<ChainNodeEventArgs> DependencyChanged
        {
            add { dependencyChangedEvent.Add(value); }
            remove { dependencyChangedEvent.Remove(value); }
        }

        public void Dispose()
        {
            ((INotifyPropertyChanged)base.NodeValue).PropertyChanged -= inpcHandler.Handler;
        }
    }
}


using System;

namespace ChainedObserver
{
    /// <summary>
    /// Generic base class for all Nodes used. Simply
    /// introduces a parent of type T, and passes
    /// value and name constructor params to <c>NodeBase</c> 
    /// base class
    /// </summary>
    public class GenericNodeBase<T> : NodeBase
    {
        public GenericNodeBase(T parent, object value, String name)
            : base(value, name)
        {
            this.Parent = parent;
        }


        public T Parent { get; set; }

    }
}


using System;

namespace ChainedObserver
{
    /// <summary>
    /// Provides a common base class for all nodes.
    /// Provides 2 properties :
    /// <list type="Bullet">
    /// <item>PropertyName : which is the name of the 
    /// property the node is monitoring from the overall lambda 
    /// expression provided to the 
    /// <c>ChainedPropertyObserver</c></item>
    /// <item>NodeValue : Holds a WeakReference to the property 
    /// value the node is monitoring from the overall lambda expression 
    /// provided to the <c>ChainedPropertyObserver</c></item>
    /// </list>
    /// </summary>
    public class NodeBase
    {
        private WeakReference wrNodeValue;

        public NodeBase(object value, String name)
        {
            this.PropertyName = name;
            wrNodeValue = new WeakReference(value);
        }

        public String PropertyName { get; set; }

        public object NodeValue
        {
            get
            {
                object target = wrNodeValue.Target;
                if (target != null)
                {
                    
                    return target;
                }
                return null;
            }
        }
    }
}

Parsing the Registered Chain Callbacks

When a piece of user code calls the ChainedPropertyObserver RegisterChainLinkHandler(Expression<Func<object>> handlerExpression, Action propertyChangedCallback) method, the parsing of the handler expression is handled much the same way as it was for when we parsed the original expression that was provided when the ChainedPropertyObserver.CreateChain(..) method was first called.

There are two other things that need to happen:

  1. We need to store an entry in an internal Dictionary which represents the last object seen in the handlerExpression which was supplied in the ChainedPropertyObserver.RegisterChainLinkHandler(..) method. We choose the last node as this is obviously what the user intended when they supplied the handlerExpression.
  2. We also traverse the original List<ChainNode>, trying to find the ChainNode that is a match for the specific handler the user supplied, and when we find the original ChainNode, we assign the callback delegate to it.

As elsewhere we do not actually hold a delegate (that would require a strong reference), we use a WeakDelegateReference to allow the original object to be GC'd.

Let's now have a look at the relevant code:

public void RegisterChainLinkHandler(
    Expression<Func<object>> handlerExpression, 
    Action propertyChangedCallback)
{
    handlers.Add(handlerExpression, 
                 new WeakDelegateReference(propertyChangedCallback));
    RecreateHandlers();
}

So all we do here is add the handlerExpression, and a WeakDelegateReference which wraps the original callback delegate is added to an internal dictionary, and then the RecreateHandlers() method is called. Let's have a look at that now.

private void RecreateHandlers()
{
    existingPropHandlerNodes.Clear();
    foreach (KeyValuePair<Expression<Func<object>>,
        WeakDelegateReference>  handler in handlers)
    {
        propMemberSeen.Clear();
        Visit(handler.Key);
        CreatePropertyHandlerNodes();
        //only add in existing PropertyHandler if we managed to create an entire
        //chain of nodes, ie no nulls in chain
        if (workingPropHandlerNodes.Last().PropertyName == propMemberSeen.Last())
        {
            existingPropHandlerNodes.Add(workingPropHandlerNodes.Last(), handler.Value);
        }
    }
    ReCreateChainLinkHandlers();
}

It can be seen that this method simply loops through each key/value pair in the maintained Dictionary of the specific callbacks that were registered. We then traverse the handlerExpression in much the same way as we did before, and we also create a List<PropertyNode> for the handlerExpression visited.

The last piece to the puzzle is to use the just created List<PropertyNode> to look through the original List<ChainNode> and find the one that matches the last PropertyNode using predefined matching rules. When a ChainNodeis matched, it simply has its PropertyChangedCallback assigned to the WeakDelegateReference wrapped callback delegate. This is done using the ReCreateChainLinkHandlers() method, as shown below:

private void ReCreateChainLinkHandlers()
{
    foreach (KeyValuePair<PropertyNode, 
        WeakDelegateReference> propCallBack in existingPropHandlerNodes)
    {
        var matchedNodes = (from n in nodes
                            where n.PropertyName == propCallBack.Key.PropertyName &&
                            n.NodeValue.GetType() == propCallBack.Key.NodeValue.GetType()
                            select n);

        if (matchedNodes.Count() == 1)
        {
            ChainNode actualNode = matchedNodes.First();
            actualNode.PropertyChangedCallback = propCallBack.Value;
        }
    }
}

Just for completeness, this is what a PropertyNode looks like; you saw the base class previously:

using System;

namespace ChainedObserver
{
    /// <summary>
    /// A convience class that simply inherits from <c>GenericNodeBase of T</c>
    /// where T is PropertyNode
    /// </summary>
    public class PropertyNode : GenericNodeBase<PropertyNode>
    {
        #region Ctor
        public PropertyNode(PropertyNode parent, object value, String name) 
        : base(parent,value,name)
        {

        }
        #endregion
    }
}

Listening to Changes in the Chain

One of the rather neat things about the ChainPropertyObserver is that it kind of mends a broken link when a new object is assigned to any part of the already existing observer chain, which we are monitoring using the internal List<ChainNode>.

In words, what happens is as follows:

When a ChainNode nodeValue is detected as being changed, it fires its DependencyChanged event, which is listened to by the ChainPropertyObserver. When this event is seen from any of the internally held List<ChainNode>, we obtain the position of the changed ChainNode within the internally held List<ChainNode>, remove any nodes after and including it from the overall List<ChainNode>, and then recreate the rest of the changed chain using the new object graph using a tiny bit of Reflection (basically, the same as we used to create the initial List<ChainNode> by traversing the list of membersSeen as we went through already).

We also re-hook up any specific property change to the ChainNodes that were registered.

By carrying out these steps, we attempt to recreate the original chain that we wish to observe (as specified by the original lambda) and also attempt to hook up any specific callbacks that were registered to the current objects in the List<ChainNode>.

Time for some code, I feel.

If we look at what happens when the ChainPropertyObserver detects that one of its observed ChainNodes has changed:

private void Parent_DependencyChanged(object sender, ChainNodeEventArgs e)
{
    int indexOfChangedNode = nodes.IndexOf(e.Node);

    for (int i = indexOfChangedNode; i < nodes.Count; i++)
    {
        nodes[i].DependencyChanged -= Parent_DependencyChanged;
    }
    RecreateNodes((ChainNode)e.Node.Parent, indexOfChangedNode);


    Action<string> callback = (Action<string>)overallChangedCallback.Target;
    if (callback != null)
    {
        callback(e.Node.PropertyName);
    }
}

We can see that we work out the index of the ChainNode that changed, and then make a call to the RecreateNodes(..) method, where the changed part of the chain will be removed and a new section of the chain will be created to replace the changed section.

private void RecreateNodes(ChainNode oldParent, int indexOfChangedNode)
{
    //remove changed section of chain, and also clean the removed node up
    for (int i = nodes.Count - 1; i >= indexOfChangedNode; i--)
    {
        ChainNode node = nodes[i];
        nodes.Remove(node);
        node.Dispose();
        node = null;
    }

    //recreate new section of chain
    ChainNode parent = oldParent;
    bool chainBroken = false;
    for (int i = indexOfChangedNode; i < membersSeen.Count; i++)
    {
        parent = (ChainNode)CreateNode(parent, membersSeen[i], 
        NodeType.ChainNode, out chainBroken);
        //only carry on creating the chain while the chain is 
    //intact (ie no null references seen)
        if (!chainBroken)
        {
            nodes.Add(parent);
        }
        else
            break;
    }

    for (int i = indexOfChangedNode; i < nodes.Count; i++)
    {
        nodes[i].DependencyChanged += Parent_DependencyChanged;
    }
    //recreate and attach specific callback handlers
    RecreateHandlers();
}

After the ChainNodes are recreated and reassigned the specific callbacks, we simply call the overall changed callback that was supplied when the user called the ChainPropertyObserver.CreateChain(..) method.

How the Specific Callbacks Fire

OK, so we have done loads, but you are probably wondering how the specific callback that we assigned (as already described) is actually called. This is easy; it's all down to the following code in the ChainNode, which is the only node type that calls back the specific change callback that is registered.

public class ChainNode : GenericNodeBase<ChainNode>, IDisposable
{
    private WeakEventHandler<PropertyChangedEventArgs> inpcHandler;

    public ChainNode(ChainNode parent, object value, String name) : base(parent,value,name)
    {
        inpcHandler = new WeakEventHandler<PropertyChangedEventArgs>(PropertyChanged);
        ((INotifyPropertyChanged)value).PropertyChanged += inpcHandler.Handler;
    }

    public WeakDelegateReference PropertyChangedCallback { get; set; }

    private void PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //raise overall dependencyChangedEvent
        dependencyChangedEvent.Raise(this, new ChainNodeEventArgs(this));

        //now callback specific handler for this Node type/name
        if (PropertyChangedCallback != null)
        {
            Action callback = (Action)PropertyChangedCallback.Target;
            if (callback != null)
            {
                callback();
            }
        }
    }

    public void Dispose()
    {
        ((INotifyPropertyChanged)base.NodeValue).PropertyChanged -= inpcHandler.Handler;
    }
}

Dealing with Nulls in the Chain

When I first published this article, a reader correctly stated that it would only work if all of the objects in the chain were pointing to non-null object references, which is correct, an oversight for sure.

I had a small think about that, and have now reworked the code to deal with nulls. I will not go into all the inner workings of this, but there are a few rules that are adhered to in the attached code, which we will go through below. Understanding these rules should be enough to show you how it works internally:

Say you have an object graph that looks like this:

Person p = new Person();
p.Age = 12;
p.Name = "sacha";
p.Address = new Address() { 
    Addressline1="21 maple street",
    Addressline2 = "Hanover",
    GeoLocation = new GeoLocation { Longitude=0.555, Latitude=0.787 } };

Let's now look at a few use cases:

  1. If we have a specific listener registered on GeoLocation, and Address is set to null, we will not get a callback on the GeoLocation specific callback until we have a GeoLocation object in the chain that is assigned to a non-null Address object
  2. If we have a specific listener registered on Address, and GeoLocation is set to null, we will still get a callback on the Address specific callback as GeoLocation is further down the chain.

I think that pretty much sums up how null object references are dealt with in a chain; the code in this article shows more details as does the console demo at the bottom of this article.

Cleanse

The ChainPropertyObserver does use WeakReferences/WeakEvents almost everywhere, but there is also a simple house cleaning method that you can use when you are completely finished with the ChainPropertyObserver, which will simply call Dispose() on all its currently held List<ChainNode>.

You can use this house cleaning as simply as this:

ChainPropertyObserver.Clense();

Known Issues / The Importance of Weakness

As you can imagine, maintaining a chain of objects where any part of the chain could be swapped out under your feet at any moment is bad enough, but the attached demo code not only has to do that, it needs to know when this occurs, and re-attach a registered callback handler delegate reference to the new object in the chain.

Now if you just read that paragraph and thought nothing was up with all of that, think again. We have a chain of objects A->B->C and we are not only holding references, but we are hooking up callback handlers to these objects, but what happens when the chain changes to A->B1-C? We still hold a reference to B somewhere. Does that not sound like this little supposedly helpful utility of ours is hanging on to object references? Sure sounds like it, right?

If you think so too, you would be correct, and it is a real concern, so we need to do something about that. What can we do? Well luckily, the .NET Framework has the answer in the form of the WeakReference class, which is a very helpful class that allows us to wrap an object reference, but the original object we wrapped can still be garbage collected.

Within the ChainPropertyObserver code that I present in this article, nearly everything is weak, from the holding of object references in the chain (which allows old redundant chain objects to be GCd), to the holding of callback delegates, to custom events... they are all weak. Basically, weak is good in the scenario that the ChainPropertyObserver is trying to solve. Let's see some examples, shall we?

Lambda Object References

When we first visit the expression tree constant nodes, we use WeakReferences to hold the constants (the object at the very top of the object graph the lambda is representing) as can be seen from the following code from the ChainPropertyObserver:

protected override Expression VisitConstant(ConstantExpression node)
{
    if (!isInitialised)
    {
        wrRoot = new WeakReference(node.Value);
    }
    else
    {
        wrPropRoot = new WeakReference(node.Value);
    }
    return base.VisitConstant(node);
}

ChainNode/PropertyNode Object References

As we know, each NodeBase item (and ChainNode/PropertyNode both inherit from NodeBase) in the chain will hold a NodeValue. This is also the case when constructing the registered callback chains too, so let's see how these objects are stored against a NodeBase object.

The actual NodeBase object looks like this:

public class NodeBase
{
    private WeakReference wrNodeValue;

    public NodeBase(object value, String name)
    {
        this.PropertyName = name;
        wrNodeValue = new WeakReference(value);
    }
    public String PropertyName { get; set; }

    public object NodeValue
    {
        get
        {
            object target = wrNodeValue.Target;
            if (target != null)
            {
                    
                return target;
            }
            return null;
        }
    }
}

And when we create a NodeBase derived class (either PropertyNode or ChainNode) we would do something like this:

new ChainNode((ChainNode)parent, nodeValue, propNameForNewChild);

Where the nodeValue is a reference to an object in the original chain Expression. So we really need to wrap that object reference up in a WeakReference. Luckily, the NodeBase class does that for us, as shown in the code snippet for NodeBase above.

Registered Callbacks

Another area where we need WeakReferences is when we register the specific callback delegates that will eventually be assigned to a ChainNode. We handle this using a custom WeakDelegateReference.

Here is how the callbacks are registered:

public void RegisterChainLinkHandler(
    Expression<Func<object>> handlerExpression, 
    Action propertyChangedCallback)
{
    handlers.Add(handlerExpression, new WeakDelegateReference(propertyChangedCallback));
    .....
}

And here is the WeakDelegateReference code:

using System;
using System.Reflection;

namespace ChainedObserver.Weak
{
    /// <summary>
    /// A simple weak Delegate helper, that will maintain a Delegate
    /// using a WeakReference but allow the original Delegate to be
    /// reconstrcuted from the WeakReference
    /// </summary>
    public class WeakDelegateReference
    {
        private readonly Type delegateType;
        private readonly MethodInfo method;
        private readonly WeakReference weakReference;

        public WeakDelegateReference(Delegate @delegate)
        {
            if (@delegate == null)
            {
                throw new ArgumentNullException("delegate");
            }
            this.weakReference = new WeakReference(@delegate.Target);
            this.method = @delegate.Method;
            this.delegateType = @delegate.GetType();
        }

        public Delegate Target
        {
            get
            {
                return this.TryGetDelegate();
            }
        }

        private Delegate TryGetDelegate()
        {
            if (this.@method.IsStatic)
            {
                return Delegate.CreateDelegate(this.@delegateType, null, this.@method);
            }
            object target = this.@weakReference.Target;
            if (target != null)
            {
                return Delegate.CreateDelegate(this.@delegateType, target, this.@method);
            }
            return null;
        }
    }
}

Chain Node Dependency Changes

Another area of concern is the fact that each item in the chain of objects that we are holding a WeakReference for (thanks to NodeBase) to is an INPC based object which exposes a PropertyChanged event that is being listened to. Now as I have also stated, the ChainPropertyObserver is also capable of reconstructing a chain whenever a new object is introduced into the chain, but we still hooked up a listener to the old object's INPC PropertyChanged. Sounds like trouble to me.

So we have to ask ourselves, if we are hooking into some old object's PropertyChanged event, should we not also be unhooking? Or even better, listen to all the INPC objects using some sort of weak event handler. This second approach is the one I choose to do.

Again, this happens automatically for you, both by holding a WeakReference object for NodeValue which is done in NodeBase, and the weak event listening is done in ChainNode. If we look at the relevant parts from the ChainNode code, we can see how the NodeBase NodeValue WeakReferenced object's INPC PropertyChanged event is listened to using a weak event listener. The weak event listener is by fellow WPF disciple Paul Stovell.

I also unhook the weak event listener in an implementation of the IDisposable which is implemented by ChainNode.

The other thing that is done in ChainNode is that it also exposes a DependencyChanged event in response to the NodeBase NodeValue WeakReferenced object's INPC PropertyChanged event changing. This ChainNode.DependencyChanged event is what the ChainPropertyObserver uses internally. This event is actually a WeakEvent right in the ChainNode. This WeakEvent is from Daniel Grunwald's excellent CodeProject article: WeakEvents.aspx

Anyway, here is the relevant code from ChainNode that deals with all this:

using System;
using System.ComponentModel;

using ChainPropertyObserver.Weak;
using ChainPropertyObserver.WeakEvents;

namespace ChainPropertyObserver
{
    /// <summary>
    /// Represents a node in the original Lambda expression that was
    /// passed to the <c>ChainPropertyObserver</c>. This class also
    /// provides an overall DependencyChanged weak event 
    /// (using Daniel Grunwalds<c>WeakEvent</c>) that is raised whenever
    /// the property that this <c>ChainNode</c> is monitoring changes.
    /// Basically this node hooks a <c>NotifyPropertyChanged</c>
    /// listener to a source object to know when a overall dependency changes.
    /// When the dependency changes the DependencyChanged is raied which 
    /// allows an overall callback delegate to be called when any node in the
    /// chain within the original Lambda expression changes. There is also
    /// a callback here may run a specific callback handler that was
    /// registered within the <c>ChainPropertyObserver</c>. This callback
    /// may or may not be null for any given <c>ChainNode</c>, it depends
    /// on what property this <c>ChainNode</c> is monitoring from the original
    /// Lambda expression, as to whether there will be an active specific
    /// callback assigned for a given <c>ChainNode</c>
    /// 
    /// When Dispose() is called this class will
    /// unhook any <c>NotifyPropertyChanged</c>
    /// listener that it has previously subscribed to
    /// </summary>
    public class ChainNode : GenericNodeBase<ChainNode>, IDisposable
    {
        private WeakEventHandler<PropertyChangedEventArgs> inpcHandler;
        public ChainNode(ChainNode parent, object value, String name) : 
                         base(parent,value,name)
        {
            inpcHandler = 
              new WeakEventHandler<PropertyChangedEventArgs>(PropertyChanged);
            ((INotifyPropertyChanged)value).PropertyChanged += inpcHandler.Handler;
        }

        public WeakDelegateReference PropertyChangedCallback { get; set; }

        private void PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //raise overall dependencyChangedEvent
            dependencyChangedEvent.Raise(this, new ChainNodeEventArgs(this));

            //now callback specific handler for this Node type/name
            if (PropertyChangedCallback != null)
            {
                Action callback = (Action)PropertyChangedCallback.Target;
                if (callback != null)
                {
                    callback();
                }
            }
        }

        private readonly WeakEvent<EventHandler<ChainNodeEventArgs>> 
        dependencyChangedEvent =
                    new WeakEvent<EventHandler<ChainNodeEventArgs>>();

        public event EventHandler<ChainNodeEventArgs> DependencyChanged
        {
            add { dependencyChangedEvent.Add(value); }
            remove { dependencyChangedEvent.Remove(value); }
        }

        public void Dispose()
        {
            ((INotifyPropertyChanged)base.NodeValue).PropertyChanged -= inpcHandler.Handler;
        }
    }
}

Anyway, I hope going through these areas of concern and how the ChainPropertyObserver handles these scenarios sets your mind at rest.

How to Use it

I think the code presented in this article is pretty easy to use, and basically comes down to these three steps:

  1. Create the ChainPropertyObserver
  2. This is done just like this:

    chainPropertyObserver = new ChainPropertyObserver();
  3. Create some chain you wish to monitor, with an overall changed callback
  4. This is done just like this:

    chainPropertyObserver.CreateChain(() => MyPerson.Address.GeoLocation, Changed);
  5. Optionally register a specific callback handler for a part of the chain
  6. This is done just like this:

    //And now hook up some specific Chain Listeners
    chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson.Address,
    () =>
    {
        //Do whatever
    });

The ChainPropertyObserver also has a Clense() method that you should call when you are completely done with the ChainPropertyObserver. I have not shown this in the demos, as it is essentially your call when to call this Clense() method; that is up to you. As I have also said, all object references/events etc., are done using WeakReferences so there should be no issues with memory being held, but you should call this method if you can.

Note: Some people will more than likely ask why I did not make it implement IDisposable which would allow the use of a using(..) statement, but that would be weird, as the code would look something like this if I were to allow the use of a using(..) statement by implementing IDisposable:

//create chainPropertyObserver
using (new ChainPropertyObserver())
{
    chainPropertyObserver.CreateChain(() => 
               MyPerson.Address.GeoLocation, Changed);

    //And now hook up some specific Chain Listeners
    chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson,
        () =>
        {
            personChanges.Add(MyPerson.ToString());
        });

    chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson.Address,
        () =>
        {
            addressChanges.Add(MyPerson.Address.ToString());
        });

    chainPropertyObserver.RegisterChainLinkHandler(() => 
         MyPerson.Address.GeoLocation, () =>
        {
            geoLocationChanges.Add(MyPerson.Address.GeoLocation.ToString());

        });
}

So what would happen there is that the IDisposable.Dispose() method would be called at the end of the using(..) statement and the ChainPropertyObserver would not work correctly. So I opted for a manual cleanup method, sorry about that.

Anyway, now that you know how to use the attached code, let's have a quick look at the two demos I provide with the download code.

Console Demo

This is a pretty simple console app example, where we follow the three mandatory steps I just described. Here is the complete demo code:

using System;
using System.Collections.Generic;
using CommonModel;

namespace ChainedObserver.Test
{
    class Program
    {
        public Person MyPerson { get; set; }

        public void Run()
        {
            //create INPC model
            Person p = new Person();
            p.Age = 12;
            p.Name = "sacha";
            p.Address = new Address() { 
                Addressline1="21 maple street",
                Addressline2 = "Hanover",
                GeoLocation = new GeoLocation { Longitude=0.555, Latitude=0.787 } };

            MyPerson = p;

            //create observer
            ChainPropertyObserver chainPropertyObserver = new ChainPropertyObserver();
            chainPropertyObserver.CreateChain(() => 
                      MyPerson.Address.GeoLocation, Changed);

            List<string> personChanges = new List<string>();
            List<string> addressChanges = new List<string>();
            List<string> geoLocationChanges = new List<string>();

            //create some INPC listeners
            chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson,
                () =>
                {
                    Console.WriteLine("The Person that changed is now : {0}", 
                                      MyPerson.ToString());
                    personChanges.Add(MyPerson.ToString());
                });

            chainPropertyObserver.RegisterChainLinkHandler(() => MyPerson.Address,
                () =>
                {
                    Console.WriteLine("The Address that changed is now : {0}", 
                        MyPerson.Address.ToString());
                    addressChanges.Add(MyPerson.Address.ToString());
                });

            chainPropertyObserver.RegisterChainLinkHandler(() => 
                  MyPerson.Address.GeoLocation, () =>
                {
                    Console.WriteLine("The GeoLocation that changed is now : {0}", 
                        MyPerson.Address.GeoLocation.ToString());
                    geoLocationChanges.Add(MyPerson.Address.GeoLocation.ToString());

                });

            //Chain the Chain data, including setting new objects in the chain
            MyPerson.Address = new Address()
            {
                Addressline1 = "45 there street",
                Addressline2 = "Pagely",
                GeoLocation = new GeoLocation { Longitude = 0.5, Latitude=0.5 }
            };

            MyPerson.Address = new Address()
            {
                Addressline1 = "101 new town road",
                Addressline2 = "Exeter",
                GeoLocation = new GeoLocation { Longitude = 0.5, Latitude = 0.5 }
            };

            MyPerson.Address = null;

            MyPerson.Address = new Address()
            {
                Addressline1 = "12 fairweather Road",
                Addressline2 = "Kent",
                GeoLocation = new GeoLocation { Longitude = 0.5, Latitude = 0.5 }
            };

            MyPerson.Address = null;

            MyPerson.Address = new Address()
            {
                Addressline1 = "45 plankton avenue",
                Addressline2 = "bristol",
                GeoLocation = new GeoLocation { Longitude = 0.5, Latitude = 0.5 }
            };
 
            MyPerson.Address.GeoLocation = 
              new GeoLocation { Longitude = 0.49, Latitude = 0.49 };
            MyPerson.Address.GeoLocation = 
              new GeoLocation { Longitude = 0.49, Latitude = 0.49 };
            MyPerson.Address.GeoLocation = null;
            MyPerson.Address.GeoLocation = 
              new GeoLocation { Longitude = 0.66, Latitude = 0.71 };
            MyPerson.Address.GeoLocation.Longitude = 0.51;
            MyPerson.Address.GeoLocation.Longitude = 0.52;
            MyPerson.Address.GeoLocation.Longitude = 0.53;
            MyPerson.Address.GeoLocation.Longitude = 0.54;
            MyPerson.Address.GeoLocation.Longitude = 0.54;
            Console.ReadLine();
        }

        private void Changed(string propertyThatChanged)
        {
            Console.WriteLine("The property that changed was : {0}", 
                              propertyThatChanged);
        }

        static void Main(string[] args)
        {

            Program p = new Program();
            p.Run();

        }
    }
}

The most important part of this demo code is to actually prove things worked as expected with the changes to the objects in the chain. To recap, we did this:

//Chain the Chain data, including setting new objects in the chain
MyPerson.Address = new Address()
{
    Addressline1 = "45 there street",
    Addressline2 = "Pagely",
    GeoLocation = new GeoLocation { Longitude = 0.5, Latitude=0.5 }
};

MyPerson.Address = new Address()
{
    Addressline1 = "101 new town road",
    Addressline2 = "Exeter",
    GeoLocation = new GeoLocation { Longitude = 0.5, Latitude = 0.5 }
};

MyPerson.Address = null;

MyPerson.Address = new Address()
{
    Addressline1 = "12 fairweather Road",
    Addressline2 = "Kent",
    GeoLocation = new GeoLocation { Longitude = 0.5, Latitude = 0.5 }
};

MyPerson.Address = null;

MyPerson.Address = new Address()
{
    Addressline1 = "45 plankton avenue",
    Addressline2 = "bristol",
    GeoLocation = new GeoLocation { Longitude = 0.5, Latitude = 0.5 }
};
 
MyPerson.Address.GeoLocation = new GeoLocation { Longitude = 0.49, Latitude = 0.49 };
MyPerson.Address.GeoLocation = new GeoLocation { Longitude = 0.49, Latitude = 0.49 };
MyPerson.Address.GeoLocation = null;
MyPerson.Address.GeoLocation = new GeoLocation { Longitude = 0.66, Latitude = 0.71 };
MyPerson.Address.GeoLocation.Longitude = 0.51;
MyPerson.Address.GeoLocation.Longitude = 0.52;
MyPerson.Address.GeoLocation.Longitude = 0.53;
MyPerson.Address.GeoLocation.Longitude = 0.54;
MyPerson.Address.GeoLocation.Longitude = 0.54;

Now based on that, I would expect to see the following to occur. Notice how the ChainedPropertyObserver recovers after seeing a null object reference in the chain; it just recovers.

  1. Person changes due to a new Address object getting assigned (Person changes = 1)
  2. Person changes due to a new Address object getting assigned (Person changes = 2)
  3. Person changes due to a null Address object getting assigned (Person changes = 3)
  4. Person changes due to a new Address object getting assigned (Person changes = 4)
  5. Person changes due to a null Address object getting assigned (Person changes = 5)
  6. Person changes due to a new Address object getting assigned (Person changes = 6)
  7. Person.Address changes due to a new GeoLocation object getting assigned (Person.Address changes =1)
  8. Second Person.Address GeoLocation change is ignored as it is the same data as the previous GeoLocation object (Person.Address changes =1)
  9. Person.Address.GeoLocation changes due to a null GeoLocation object getting assigned (Person changes = 2)
  10. Person.Address changes due to a new GeoLocation object getting assigned (Latitude 0.66, Longitude 0.71) (Person.Address changes =3)
  11. Person.Address.GeoLocation changes due to a GeoLocation data object value changing to 0.51 (Person.Address.GeoLocation changes =1)
  12. Person.Address.GeoLocation changes due to a GeoLocation data object value changing to 0.52 (Person.Address.GeoLocation changes =2)
  13. Person.Address.GeoLocation changes due to a GeoLocation data object value changing to 0.53 (Person.Address.GeoLocation changes =3)
  14. Person.Address.GeoLocation changes due to a GeoLocation data object value changing to 0.54 (Person.Address.GeoLocation changes =4)
  15. Fifth Person.Address.GeoLocation change is ignored as it is the same data as the previous GeoLocation object (Person.Address.GeoLocation changes =4)

So let's see a screenshot of the demo code to see if our expectations hold true. We are expecting the following changes to have occurred:

  • Person changes =6
  • Address changes = 3
  • GeoLocation changes = 4

Here are some screenshots of the demo app in Debug, which proves this to be true (unit tests would have been better, sorry):

We are at the end after all changes have been made, and we can see only 6 Person changes:

And only 3 Address changes:

And 4 GeoLocation changes:

This is pretty cool. We only got the change notifications we were expecting. The ChainPropertyObserver was totally fine with new objects being swapped for existing objects in the chain, and it was completely happy and knew how to deal with that, and still provided us with the correct INPC callbacks that we registered interest in.

I say this is pretty useful actually.

WPF Demo

I have also crafted a more elaborate demo, binding the object graph up like {Binding Person.Address.GeoLocation} which is something I do not like so much, but it does demonstrate the demo quite well. The WPF demo looks like this, and allows you to create changes manually or using several Buttons provided, to see what properties are expected to change when a Button is clicked. You should consult the code.

This demo included ListBoxes for the following INPC changes, so you can see what happens when you change something manually or by using the Buttons provided.

As I say, you will need to consult the code for a better understanding of what changes take place when you click a Button. It has been setup quite specifically.

That's it For Now

That is all I have to say right now. Like I say, I can not make my mind up if this is truly useful, or useless, but nonetheless, I think it is an interesting piece of code that shows you how you can build up chains of objects using Lambda expression trees, and it also demonstrates some interesting weak event/delegate stuff, so for those reasons alone, I am glad I published this article... If you like it and can see a use for it, please tell me about it, and if you feel uber inclined, votes/cash/beer/old acid house records and wenches are always welcome... will also accept a lama.

History

  • 09/03/11: Initial issue.
  • 10/03/11: Changed implementation to cope with null object references as part of the chain. The article talks about this.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here