Home > C#, Dependencies > Tracking Inter-Object Dependencies using Expression Trees (Part 1)

Tracking Inter-Object Dependencies using Expression Trees (Part 1)

December 28th, 2008

This is the first part of an article that covers the usage of lambda expressions to monitor changes on complex object graphs.

The second part can be found here.
The third part (Lambda Bindings) can be found here.

 

Latest Version: 1.0.3, 2009.04.03
Download Project: lambda-dependencies.zip

Introduction

In an ongoing project, I had to struggle with quite a few dependencies between objects, mainly with regards to validation. As a result, I came up with a generic solution based on lambda expressions.

But let’s start with an example. Imagine you have an application that references a Student that attends a a given School. And let’s assume, the school’s location (city) is of importance to you, so a changed city should trigger some action. My class model looks like this:

image

 

In order to keep track of the dependency on the school’s location, you have to observe quite a few things:

  • Whether the City property of the school’s Address object is changed.
  • Whether the Address object of the school itself is replaced by another instance.
  • Whether the referenced School object of the student is changed. This would require you to re-wire change listeners for the school and address.
  • Whether the referenced student instance itself is being replaced by setting the MyStudent property. Again, you would have to deregister all existing listeners and create new ones for the new student, school, and address.

 

This is a lot of error-prone and tedious work: You need to register and de-register event listeners, handle null references (a new School instance might not provide an Address at first) etc. This is where expression trees come to the rescue. Basically, you can reduce the plumbing to a single line of code:

 

public MyApplication()
{
  //create a dependency on the city of the student's school
  DependencyNode<string> dependency;
  dependency = DependencyNode.Create(() => MyStudent.School.Address.City);
}

 

The snippet above analyzes the submitted expression and resolves the dependency graph, which basically looks the following:

image

Dependency.Create returns an object of type DependencyNode. This node refers to the root of the expression tree, which is the MyApplication class that provides the MyStudent property. You can traverse the dependency chain in both directions through  the ChildNode and ParentNode properties should you ever want to. But usually (if at all!), you’ll need just two properties:

  • LeafValue returns the dependency target. In our sample, this is the string that is returned by the City property of the school’s Address.
  • If the school’s Address (or the School reference itself) would be set to null, there would not be a city at all. In this case, LeafValue just returns a value of null. However: You can check whether the graph from root to the dependency target is intact by evaluating the IsChainBroken property on any given node.

 

//get the city, if the dependency graph leads to a valid address
string city = dependency.IsChainBroken ? "NONE" : dependency.LeafValue;

(Note that LeafValue is a generic property. In this case, it is of type string)

image

 

Handling Changes in the Dependency Chain

As soon as a dependency is declared, the DependencyNode tries to register event listeners on all objects of the dependency graph in order to track changes and keep itself up-to-date. Furthermore, you can register an event listener with the node’s DependencyChanged event, which fires whenever a change in the dependency chain occurs. Event listeners receive an instance of DependencyChangeEventArgs that provide the changed node, property or field names, and the change reason:

image

 

Here’s a sample – the snippet below registers an event listener which basically just outputs the name of the student, the school name, and the city to the console:

 

public MyApplication()
{
  //create a dependency on the city of the student's school
  dependency = DependencyNode.Create(() => MyStudent.School.Address.City);
  dependency.DependencyChanged += OnStudentCityChanged;
}


/// <summary>
/// Invoked whenever the dependency graph between <see cref="MyStudent"/>
/// property and the <see cref="Address.City"/> of the student's
/// school is being changed.
/// </summary>
private void OnStudentCityChanged(object sender, DependencyChangeEventArgs<string> e)
{
  //get the changed node
  DependencyNode<string> changedNode = e.ChangedNode;

  //get student name, if we have one after all
  string studentName = myStudent == null ? "[none]" : myStudent.Name;

  //get the school name
  string school = "[none]";
  if (myStudent != null && myStudent.School != null)
  {
    school = myStudent.School.SchoolName;
  }

  //get the city, if the dependency graph leads to a valid address
  string city = changedNode.IsChainBroken ? "[unavailable]" : changedNode.LeafValue;

  //NOTE: You can also get the leaf value through this convenience method:
  //string city = e.TryGetLeafValue("[unavailable]");

  //write student/city to console
  string msg = "Student {0} goes now to school {1} in {2}";
  Console.Out.WriteLine(msg, studentName, school, city);
}

 

Once an event listener is registered, you can change properties on different levels of the dependency chain:

public void Test()
{
  //assign a student
  MyStudent = new Student { Name = "Lucy" };
  //set a school without an address
  MyStudent.School = new School {SchoolName = "University"};
  //assign an address
  MyStudent.School.Address = new Address {City = "Redmond"};
  //assign another address instance
  MyStudent.School.Address = new Address {City = "New York"};
  //change the City property of the address
  MyStudent.School.Address.City = "Washington";
  //cut the graph by removing the school reference
  MyStudent.School = null;
  //clear the MyStudent property completely
  MyStudent = null;
}

 

Every line of the above test method triggers the change event. Accordingly, the console output is the following:

Student Lucy goes now to school [none] in [unavailable]
Student Lucy goes now to school University in [unavailable]
Student Lucy goes now to school University in Redmond
Student Lucy goes now to school University in New York
Student Lucy goes now to school University in Washington
Student Lucy goes now to school [none] in [unavailable]
Student [none] goes now to school [none] in [unavailable]

 

Enjoy :-)

kick it on DotNetKicks.com


Author: Categories: C#, Dependencies Tags: ,
  1. reno
    February 25th, 2009 at 05:42 | #1

    Awesome I love it!

  2. May 15th, 2009 at 21:05 | #2

    Great approach. This seems like a completely different approach on achieving the same results (object dependencies) as I explained in this post: “Topological Sorting and Cyclic Dependencies”
    http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html

  3. liviu
    October 2nd, 2009 at 11:18 | #3

    Hi,
    I used the library and it is great but there is space for improvements:

    the binding does not set the value also.
    I had to define a wrapper to have the binding set the value in one line of code.
    I use lambdabindings to define string typed Commands that are agnostic of WPF, Windows forms etc. Now i can define:

    var cmd =
    Command.CaptionIs(“Show details”)
    .EnabledWhen(()=>User.IsAuthenticated && this.DataLoaded)
    .Executes(PrepareGrid);

  4. April 4th, 2010 at 17:01 | #4

    That’s truely excellent – can’t wait to put it to use.

  5. April 5th, 2010 at 09:54 | #5

    David,
    Thanks for your kind words – happy coding :)

  6. September 3rd, 2010 at 10:19 | #6

    Phillip,

    why do you omit the check for changes at the implementation of the ‘Address’ property of the ‘School’ class?

    With best regards

    Gerhard

  1. March 25th, 2009 at 00:59 | #1