Demo code source : ChainedPropertyObserver.zip (289 Kb)

Introduction

I have mixed feelings in writing up this article, 1/2 of me thinks it is a really cool idea and 1/2 of me 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 Frerik no disrespect intended.

Like I say I may be presenting something here that some folk don't site 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.

So 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 a interface that is quite common place in pretty much most UI programmig in windows, it is the INotifyPropertyChanged interface which allows object that implements 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 GeoLocation 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 a 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 we would 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 heirachy is correct, but as you can see there is definately a chain of related objects there.

So now that we have a 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 when ever a Persons details change, we should do some action.

To clarify what I mean by "When a Persons data changes", what I really mean is if the Persons data changes, or the Persons Address changes, or the Persons Addresses GeoLocation 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 Persons 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 3 listeners (one for Person one for the Persons Address, and one for the Address GeoLocation) in some top level class which held 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 rehook a new INPC event handler for the Persons Address.

Ok weak events can help with here, so we do not nessecarily have to worry about unhooking, but we would still need to rehook to the new Persons 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 make it possible to more easily monitor a change anywhere in a chain of objects, in fact you can choose what to 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 at 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 from my fellow WPF Disciple Philip Sumi, who had the idea to allow 2 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 philips 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 supplying by the lambda expression, these are represented internally as a List<ChainNode>. The List<ChainNode> 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 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 actual objects which we store against the actual ChainNode instances.
  3. When the specific callback handlers are added we lookup the the ChainNode that has the same object type and property name as the callback and assign the specific callback delegate that was registers 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 rehook up any specific  property change to the ChainNode, as described in step 3

In essence what the ChainPropertyObserver holds internally is one sets 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.

Lets consider a quick diagram or 2

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 are 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 3 ChainNodes configured as such

  1. ChainNode with object reference to Person object property of which ever object held the Person instance
  2. ChainNode with object reference to Address object property of the Person object in previous ChainNode
  3. ChainNode with object reference to GeoLocation object property of the Address object in 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 object reference to Person object property of which ever object held the Person instance
  2. PropertyNode with object reference to Address object property of the Person object in previous PropertyNode

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

  1. PropertyNode with object reference to Person object property of which ever object held the Person instance
  2. PropertyNode with object reference to Address object property of the Person object in previous PropertyNode
  3. PropertyNode with object reference to GeoLocation object property of the Address object in 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. So lets crack on and have a look at that.

 

Parsing The Original Chain

So 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 is whatever test class this is, and we supply a lambda expression to the ChainPropertyObserver.CreateChain(..) taking a overall chain, and a overall change handler delegate. So what happens after that.

Well lets 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 lets 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 2 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 2 lists as we use these same visit methods to create ChainNode and PropertyNode, so we just store things in 2 internal lists, "memberSeen" for ChainNodes and "propMemberSeen" for PropertyNodes.

Visit ConstantExpression

It can be seen that we store 2 things, when we visit a ConstantExpression we maintain a 2 objects which are really just the original objects that hold the source objects we have seen in the expression tree. We maintain 2 objects as we use these same visit methods to create ChainNode and PropertyNode, so we just store things in 2 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 inherit from NodeBase) are created using a bit of relection, where we make use of the wrRoot WeakReference and the membersSeen List<string>. These 2 bits of information are enough to allow us to full reflect out an actual chain of objects, which we store in a List<ChainNode>, which internally also use 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 peice 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 orginal expression that was provided when the ChainedPropertyObserver.CreateChain(..) method was first called.

There are 2 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 orginal 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.

Lets 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 are added to an internal dictionary, and then the RecreateHandlers() mehod 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 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 peice 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 ChainNode is 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 is kind of mends a broken link when a new object is assigned to any part of the already 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 rehook up any specific  property change to the ChainNode, that were registered.

By carrying out these steps the attempts to recreate the original chain that we wish to observer (as specified by original lambda) and also attempts 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 Do 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) are actually called. This is easy its all down to the following code in the ChainNode, which is the only node type that calls back the specific change callback that are 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 1st 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 was 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 hould 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 } };

Lets 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 an 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 an 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.

 

Clense

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 calls 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 maintained a chain of objects where any part of the chain could be swapped out under you feet at any moment is bad enough, but the attached demo code not only has to do that, it needs to know when this occcurs, 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 object, but what happens when the chain changed 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, so 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 (object at very top of object graph lmabda 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 a 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 objects INPC PropertyChanged, sounds like trouble to me.

So we have to ask ourselves if we are hooking into some old objects PropertyChanged event, should we not also be unhooking? Or even better listening to all the INPC objects using some sort of weak event handler. This 2nd 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 objects 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 is response to the NodeBase NodeValue WeakReferenced objects INPC PropertyChanged event changing. This ChainNode.DependencyChanged event is what the ChainPropertyObserver uses internally. This event is actual a WeakEvent right in the ChainNode. This WeakEvent is from Daniel Grunwalds excellent codeproject article: http://www.codeproject.com//KB/cs/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;
        }

    }
}

Ayway I hope going through this 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 3 steps

1. Create the ChainPropertyObserver

This is done just like this:

chainPropertyObserver = new ChainPropertyObserver();

2. Create some chain you wish to monitor, with an overall changed callback

This is done just like this:

chainPropertyObserver.CreateChain(() => MyPerson.Address.GeoLocation, Changed);

3. Optionally register a specific callback handler for a part of the chain

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 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 say why did you 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 codem lets have a quick look at the 2 demos I provide with the download code.

Console Demo

This is a pretty simple Console app example, where we simple follow the 3 mandatory steps as 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 to demo code to actually prove things worked as expected is the changes to the objects in the chain, so 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 new Address object getting assigned (Person changes = 1)
  2. Person changes due to new Address object getting assigned (Person changes = 2)
  3. Person changes due to null Address object getting assigned (Person changes = 3)
  4. Person changes due to new Address object getting assigned (Person changes = 4)
  5. Person changes due to null Address object getting assigned (Person changes = 5)
  6. Person changes due to new Address object getting assigned (Person changes = 6)
  7. Person.Address changes due to new GeoLocation object getting assigned (Person.Address changes =1)
  8. 2nd Person.Address GeoLocation change is ignored as it is the same data as previous GeoLocation object (Person.Address changes =1)
  9. Person.Address.GeoLocation changes null GeoLocation object getting assigned (Person changes = 2)
  10. Person.Address changes due to new GeoLocation object getting assigned (Latitude 0.66, Longitude 0.71) (Person.Address changes =3)
  11. Person.Address.GeoLocation changes due to GeoLocation data object value changing to 0.51 (Person.Address.GeoLocation  changes =1)
  12. Person.Address.GeoLocation changes due to GeoLocation data object value changing to 0.52 (Person.Address.GeoLocation changes =2)
  13. Person.Address.GeoLocation changes due to GeoLocation data object value changing to 0.53 (Person.Address.GeoLocation changes =3)
  14. Person.Address.GeoLocation changes due to GeoLocation data object value changing to 0.54 (Person.Address.GeoLocation changes =4)
  15. 5th Person.Address.GeoLocation change is ignored as it is the same data as previous GeoLocation object (Person.Address.GeoLocation  changes =4)

So let's see a screen shot 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

So here are some screen shots of the demo app in debug which proves this to be true (unit tests would have been better, sorry)

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

 

And only 3 Address change

 

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 which 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.

Anyway the WPF demo looks like this (click image for bigger version)

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

History

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

 

That's It For Now

Anyway 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 none the less I think it is an interesting peice 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 1 lama.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架