Expression API Cookbook






4.96/5 (46 votes)
A how to do Expression API cookbook
Introduction
This article is a strange one in a lot of ways, as there is no real end product with it as such, it is more of a how do I do X / recipe type article. So what are the recipes, and what will it show you, the reader how to do?
Well I am going to start with a wee story first (don't worry the point is one its way). A while back I got this email out the blue from this guy in the states who was creating a MVVM framework for Windows 8, and I have kept in contact with this dude (and he really is a dude, if I can convince him to upload an image, you will see what I mean) and we got talking about his IOC container within his MVVM framework, which is like MEF for Windows 8.
Looking through Ian's code, it was immediately obvious to me, than Ian really (and I do mean really) knew how to use the Expression API within .NET. This namespace has always intrigued me, so I talked to Ian about the merits of writing a joint kind of article, where we would effectively come up with some scenarios to solve using the Expression API.
Ian said he would be more than happy to write the code to any scenarios I could come up with, if I was happy to do the article writing side of things. This seemed a very fair way to do it, so we have done just that.
Now you may be asking what is so cool about the Expression API, well one thing I quite like is that you can literally write entire programs in it, and another thing that you see time and time again, is creating compiled lambda expressions that have been built up on the fly, which compile down to a delegate, so provide uber fast performance when compared to reflection. That is why the Expresson API can be useful (at least we feel that way).
That is essentially what this article is all about, we have a bunch of scenarios that I came up with (which I hope are good ones) which Ian has coded up. The examples range from simple property get/set through to some rather complex examples where we show you how to do things like how to create If-Then-Else Expressions and compute a HashCode for an object based of its property values.
Here is a list of the scenarios we will be covering
Change Tracker | This example will show you how to use the
Expression APIs to monitor an arbitrary object(s) for change
notifications, or
arbitrary collection(s) for change notifications. Where the object(s)
may be deeply nested within a tree like hierarchy |
Convert | This example will show you how to use the
Expression.Convert and Expression.TypeAs APIs to
carry out convert and casting operations |
HashCompute | This example will show you how to create an expression that will be able to generate a dynamic hash code based on examining a source input objects properties. |
IfThenElse | This example will show you how to create conditional Expression tree
logic using the Expression.IfThenEls
expression APIs |
MethodCall | This example will show you how to call an obects method where there are no parameters required, and shall also show a more complex example where the method being called requires parameters |
PropertyGetSet | This example will show you how to get/set an obects property, and shall also show a more complex example where we show you how to get/set a property value on a nested property deep in an objects hierarchy of properties |
WhereClause | This example will show you how to build a dynamic where clause that
can be applied to a IEnumerable<T> using the
Where(...) extension method |
We hope these examples will help to show how to do some of the more common programming tasks that you may encounter, using the Expression API.
Demo Code
In this section you will find all the examples. There is pretty much a 1-1 mapping between these sections and the actual demo code projects.
Change Tracker
This is by far the most complicated of all of the examples in what it actually provides, but conversely the actual Expression API usage is not as complicated as some of the other examples you will find here. The reason we have chosen to start with this one, is that we honestly think it's a useful piece of code (that is if you take the "Law Of Demeter" with a pinch of salt). It's a lot to take in, but please bear with us it's a journey (we hope) that is worth taking.
We think the best way to start with this one, is to explain in bullet points what we wanted to create, so here is that original list that both Ian and I agreed on before we started this article:
- The tracker should allow the tracking of many objects at once
- The tracker should either
yield
aIObservable<bool>
for theIsDirty
property (we were originally going to use Rx), or raise an overallIsDirty
event that users of the tracker could use. We opted for a simple event in the end, as we did not want to burden non Rx users with an extra Dll, and extra learning. - The tracker should be deemed "Not Dirty" if a tracked value is set back
to its original value. This would only obviously work for simple properties,
such as
Int, double, string
etc etc - Would have some sort of method to mark the overall tracker as "Not Dirty" to allow the change tracking to effectively be reset back to its original state
- There should be some sort of change tracker interface that allows a new
object to be tracked for changes by providing a Expression that could be
used to pull out the property. Where the object being requested to track
would have point to a object who supported change tracking via the use of
the
INotifyPropertyChanged
interface - There should be some sort of change tracker interface that allows a List
object(s) to be tracked for add/removing by providing a Expression that could be
used to pull out the property. Where the object being requested to track would
have point to a object who supported change tracking via the use of the
INotifyCollectionChanged
interface - There should be some sort of change tracker interface that allows a List
object(s) to be tracked for changes by providing a Expression that could NOT
only be used to pull out the property, but could also supply a
Predicate<T>
that must be satisfied before the tracked list items would be considered to contribute to the overall dirty state of the tracker.
public class IsDiryChangedArgs : EventArgs
{
/// <summary>
/// Default constructor
/// </summary>
/// <param name="isDirty">the new value for is dirty</param>
public IsDiryChangedArgs(bool isDirty)
{
IsDirty = isDirty;
}
/// <summary>
/// The new is dirty value
/// </summary>
public bool IsDirty { get; private set; }
}
public interface ITracker : IDisposable
{
/// <summary>
/// Track all properties on the specified object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectToTrack"></param>
void TrackObject<T>(T objectToTrack);
/// <summary>
/// Track a specified property on an object or a child object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProp"></typeparam>
/// <param name="objectToTrack"></param>
/// <param name="trackingExpression"></param>
void TrackObject<T, TProp>(T objectToTrack, Expression<Func<T, TProp>> trackingExpression);
/// <summary>
/// True if any of the objects being tracked is dirty
/// </summary>
bool IsDirty { get; }
/// <summary>
/// Mark all components in the tracker as clean
/// </summary>
void MarkAsClean();
/// <summary>
/// List of component trackers associated with this tracker
/// </summary>
/// <returns></returns>
IEnumerable<IComponentTracker> ComponentTrackers();
/// <summary>
/// Event is raised when the IsDirty flag is changed
/// </summary>
event EventHandler<IsDiryChangedArgs> IsDirtyChanged;
}
We feel the best place to learn how to use this would be by looking at the tests,
so let's see an example or 2 from the tests that we have provided with it:If we consider the following tests files and there relationships:

CLICK IMAGE FOR A LARGER VERSION
Demonstration Using Tests
Then examine some of the test cases, it should start to make a bit more sense.
Simple Test Cases
These test cases cover how you would add a tracker for
non list type properties that you wished to track. From these tests you should be able to see how you can track a object, or even a object which is deep down the
property tree. (Remember through that the object(s) being tracked must implement the
INotifyPropertyChanged
).
[TestClass]
public class SimpleTrackingTests
{
[TestMethod]
public void TrackFirstLayer()
{
bool eventCalled = false;
Child1 child1 = new Child1{ TestInt = 10};
Tracker tracker = new Tracker();
Assert.IsFalse(tracker.IsDirty);
tracker.IsDirtyChanged += (sender, args) => eventCalled = true;
tracker.TrackObject(child1, x => x.TestInt);
Assert.IsFalse(tracker.IsDirty);
child1.TestInt = 5;
Assert.IsTrue(tracker.IsDirty);
Assert.IsTrue(eventCalled);
eventCalled = false;
child1.TestInt = 10;
Assert.IsFalse(tracker.IsDirty);
Assert.IsTrue(eventCalled);
}
[TestMethod]
public void TrackSecondLayer()
{
bool eventCalled = false;
ChildA childA = new ChildA
{
Child1 = new Child1
{
TestInt = 10
}
};
Tracker tracker = new Tracker();
tracker.IsDirtyChanged += (sender, args) => eventCalled = true;
Assert.IsFalse(tracker.IsDirty);
tracker.TrackObject(childA, x => x.Child1.TestInt);
Assert.IsFalse(tracker.IsDirty);
childA.Child1.TestInt = 5;
Assert.IsTrue(tracker.IsDirty);
Assert.IsTrue(eventCalled);
eventCalled = false;
childA.Child1.TestInt = 10;
Assert.IsFalse(tracker.IsDirty);
Assert.IsTrue(eventCalled);
}
[TestMethod]
public void TrackThirdLayer()
{
bool eventCalled = false;
RootObject rootObject =
new RootObject
{
ChildA = new ChildA
{
Child1 = new Child1
{
TestInt = 5
}
}
};
Tracker tracker = new Tracker();
tracker.IsDirtyChanged += (sender, args) => eventCalled = true;
Assert.IsFalse(tracker.IsDirty);
tracker.TrackObject(rootObject,x => x.ChildA.Child1.TestInt);
Assert.IsFalse(tracker.IsDirty);
rootObject.ChildA.Child1.TestInt = 10;
Assert.IsTrue(tracker.IsDirty);
Assert.IsTrue(eventCalled);
eventCalled = false;
rootObject.ChildA.Child1.TestInt = 5;
Assert.IsFalse(tracker.IsDirty);
Assert.IsTrue(eventCalled);
}
[TestMethod]
public void MarkAsClean()
{
bool eventCalled = false;
ChildA childA = new ChildA
{
Child1 = new Child1 { TestInt = 10 }
};
Tracker tracker = new Tracker();
tracker.IsDirtyChanged += (sender, args) => eventCalled = true;
Assert.IsFalse(tracker.IsDirty);
tracker.TrackObject(childA, x => x.Child1.TestInt);
Assert.IsFalse(tracker.IsDirty);
childA.Child1.TestInt = 5;
Assert.IsTrue(tracker.IsDirty);
Assert.IsTrue(eventCalled);
tracker.MarkAsClean();
Assert.IsFalse(tracker.IsDirty);
eventCalled = false;
childA.Child1.TestInt = 10;
Assert.IsTrue(tracker.IsDirty);
Assert.IsTrue(eventCalled);
childA.Child1.TestInt = 5;
Assert.IsFalse(tracker.IsDirty);
Assert.IsTrue(eventCalled);
}
}
List Test Cases
These test cases cover how you would add a tracker for list type properties that you wished to track. From these tests you should be able to see how you can track a list for the following scenarios
- Simple Add/Remove (item being tracked must implement
INotifyCollectionChanged
) - Track a List where a certain
Predicate<T>
holds true - Track a nested List
[TestClass]
public class ListTrackingTests
{
[TestMethod]
public void TrackAllTest()
{
ObservableCollection<Child1> newList = new ObservableCollection<Child1>
{
new Child1 { TestInt = 5 },
new Child1 { TestInt = 10 }
};
Tracker tracker = new Tracker();
tracker.TrackObject(newList);
Assert.IsFalse(tracker.IsDirty);
newList[0].TestInt = 10;
Assert.IsTrue(tracker.IsDirty);
newList[0].TestInt = 5;
Assert.IsFalse(tracker.IsDirty);
}
[TestMethod]
public void FilterListTest()
{
ObservableCollection<Child1> testList = new ObservableCollection<Child1>();
ListObject listContainer = new ListObject
{
Child1List = testList
};
Tracker tracker = new Tracker();
tracker.TrackObject(listContainer,
c => c.Child1List.TrackList(x => x.TestInt > 0));
testList.Add(new Child1 { TestInt = 0, TestString = "Start" });
Assert.IsFalse(tracker.IsDirty);
testList.Add(new Child1 { TestInt = 1, TestString = "Start" });
Assert.IsTrue(tracker.IsDirty);
}
[TestMethod]
public void NestedFilterListTest()
{
ObservableCollection<Child1> testList = new ObservableCollection<Child1>();
ListObject listContainer = new ListObject
{
Child1List = testList
};
Tracker tracker = new Tracker();
tracker.TrackObject(listContainer,
c => c.Child1List.TrackList(x => x.TestInt > 0).TestString);
testList.Add(new Child1 { TestInt = 0, TestString = "Start" });
Assert.IsFalse(tracker.IsDirty);
testList.Add(new Child1 { TestInt = 1, TestString = "Start" });
Assert.IsTrue(tracker.IsDirty);
}
[TestMethod]
public void NestedListTest()
{
ObservableCollection<Child1> testList = new ObservableCollection<Child1>();
ListObject listContainer = new ListObject
{
Child1List = testList
};
testList.Add(new Child1 { TestString = "Start" });
Tracker tracker = new Tracker();
tracker.TrackObject(listContainer,
c => c.Child1List.TrackList().TestString);
Assert.IsFalse(tracker.IsDirty);
testList[0].TestString = "Hello";
Assert.IsTrue(tracker.IsDirty);
testList[0].TestString = "Start";
Assert.IsFalse(tracker.IsDirty);
}
}
So How Does The Tracker Work
One of the things that became obvious fairly quickly was that we would need to
have different type of trackers. So we came up with 3 of them which can be seen
in the following diagram

Where the IComponentTracker
interface looks like this:
/// <summary>
/// IComponentTracker is the interface all component trackers implements.
/// </summary>
public interface IComponentTracker : IDisposable
{
/// <summary>
/// The component being tracker
/// </summary>
object TrackedComponent { get; }
/// <summary>
/// True if the component or one of it's children is dirty
/// </summary>
bool IsDirty { get; }
/// <summary>
/// mark the component and all it's children as clean
/// </summary>
void MarkAsClean();
/// <summary>
/// A list of child trackers
/// </summary>
/// <returns></returns>
IEnumerable<IComponentTracker> ChildTrackers();
/// <summary>
/// Event that is raised when the is dirty flag is changed
/// </summary>
event EventHandler<IsDiryChangedArgs> IsDirtyChanged;
}
We will try not to outline every aspect of these classes but rather focus on the most salient points of each. However before we get into each of the 3 types of component tracker, let us just look at a common bit of code that all 3 of the component trackers make use of in order to build Expression(s) to obtain getters for the properties to track.
Here is the relevant code that essentially builds up lookup delegates against a property name for an object:
/// <summary>
/// ComponentTrackerHelper is used create property accessor and new component trackers.
/// </summary>
public class ComponentTrackerHelper
{
private readonly Dictionary<string, Func<object, object>> accessMethods = new Dictionary<string, Func<object, object>>();
/// <summary>
/// Access a named property on an object
/// </summary>
/// <param name="target"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
public object AccessProperty(object target, string propertyName)
{
Func<object, object> accessDelegate = GetPropertyAccessor(target, propertyName);
if (accessDelegate != null)
{
return accessDelegate(target);
}
throw new Exception(string.Format("Could not access property {0} on {1}", propertyName, target.GetType().FullName));
}
/// <summary>
/// Creates a new property accessor function
/// </summary>
/// <param name="target"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
public Func<object, object> GetPropertyAccessor(object target, string propertyName)
{
string fullPropertyName = target.GetType().FullName + "|" + propertyName;
Func<object, object> accessDelegate;
if (!accessMethods.TryGetValue(fullPropertyName, out accessDelegate))
{
PropertyInfo propertyInfo = target.GetType().GetProperty(propertyName);
// create the parameter for the instance parameter of type object
ParameterExpression inParameter = Expression.Parameter(typeof(object), "objectParam");
// cast the instance to it's real type
Expression castExpression = Expression.Convert(inParameter, target.GetType());
// create the property access expression
Expression propertyAccessExpression =
Expression.Property(castExpression, propertyInfo);
// cast the property access expression to an object
Expression returnCastExpression = Expression.Convert(propertyAccessExpression, typeof(object));
accessDelegate = Expression.Lambda<Func<object, object>>(returnCastExpression, inParameter).Compile();
accessMethods.Add(fullPropertyName, accessDelegate);
}
return accessDelegate;
}
/// <summary>
/// Creates a new component tracker based on the provided TrackerInfo
/// </summary>
/// <param name="target"></param>
/// <param name="path"></param>
/// <returns></returns>
public IComponentTracker CreateTracker(object target, TrackerInfo path)
{
switch (path.TrackerType)
{
case ComponentTrackerType.Node:
return new NodeComponentTracker(this, target, path);
case ComponentTrackerType.Leaf:
return new LeafComponentTracker(this, target, path);
case ComponentTrackerType.List:
return new ListComponentTracker(this, target as IEnumerable, path);
}
throw new Exception("Unknown tracker type: " + path.TrackerType);
}
}
Now on with the different types of component trackers:
LeafComponentTracker
- Tracks every change made on an object.
- The object MUST be a
INotifyPropertyChanged
implementing object - We will track old and new values such that we know if it is truly dirty
by comparing an "Original" value with a current value. This is handled by
the use of a
WeakReference
, to make sure the object is still capable of being garbage collected. - Should also support a "Not Dirty" state if returned to the the original value
Here is the the most relevant code for this type of tracker
/// <summary>
/// Initialize the object, setting up property trackers for all relavent properties
/// </summary>
/// <param name="helper"></param>
/// <param name="objectToTrack"></param>
/// <param name="trackerInfo"></param>
private void Initialize(ComponentTrackerHelper helper, object objectToTrack, TrackerInfo trackerInfo)
{
propertyTrackers = new Dictionary<string, PropertyTracker>();
this.helper = helper;
this.objectToTrack = objectToTrack;
this.trackerInfo = trackerInfo;
// listen to property change events
if (objectToTrack is INotifyPropertyChanged)
{
((INotifyPropertyChanged)objectToTrack).PropertyChanged += OnPropertyChanged;
}
// loop through all public properties
foreach (PropertyInfo propertyInfo in objectToTrack.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
// only monitor properties that are write able
if (propertyInfo.CanWrite)
{
bool trackWeakly = !(propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string));
// create a new property tracking object and a property accessor delegate
PropertyTracker propertyTracker = new PropertyTracker(trackWeakly)
// use the new property access delegate to retrieve the property from the object
object origValue = propertyTracker.PropertyAccess(objectToTrack);
// store the original value
propertyTracker.SetOriginalValue(origValue);
// save the new property tracker into the dictionary by property name
propertyTrackers[propertyInfo.Name] = propertyTracker;
}
}
}
/// <summary>
/// This is the property changed handler for the object being tracker
/// </summary>
/// <param name="sender"></param>
/// <param name="propertyChangedEventArgs"></param>
private void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
PropertyTracker propertyTrackingInfo;
// Get the property tracker associated with the property that changed
if (propertyTrackers.TryGetValue(propertyChangedEventArgs.PropertyName, out propertyTrackingInfo))
{
bool dirtyChanged = false;
bool hasOriginalValue;
object originalValue = propertyTrackingInfo.GetOriginalValue(out hasOriginalValue);
object newValue = propertyTrackingInfo.PropertyAccess(sender);
if (newValue != null)
{
// Property is reverting back to original value so setting is dirty to false
if (newValue.Equals(originalValue))
{
propertyTrackingInfo.IsDirty = false;
dirtyChanged = true;
}
// else if the property is clean we need to dirty it up
else if (!propertyTrackingInfo.IsDirty)
{
propertyTrackingInfo.IsDirty = true;
dirtyChanged = true;
}
}
else if (!hasOriginalValue)
{
// the original value was null and the new value is null so the property is now clean
if (propertyTrackingInfo.IsDirty)
{
propertyTrackingInfo.IsDirty = false;
dirtyChanged = true;
}
}
else // the new value is null and we have an original value
{
// only set to true if the property is clean
if (!propertyTrackingInfo.IsDirty)
{
propertyTrackingInfo.IsDirty = true;
dirtyChanged = true;
}
}
// only check for dirty properties if the property dirty flag changed
if (dirtyChanged)
{
IsDirty = propertyTrackers.Values.Any(x => x.IsDirty);
}
}
}
ListComponentTracker
- Tracks changes to a
ObservableCollection
(or any other collection that implenentsINotifyCollectionChanged
), such as Add/Remove/Replace/Reset - The object MUST be a
INotifyCollectionChanged
implementing object
Here is the most relevant code:
/// <summary>
/// Initialize the tracker
/// </summary>
/// <param name="helper"></param>
/// <param name="objectToTrack"></param>
/// <param name="trackerInfo"></param>
private void Initialize(ComponentTrackerHelper helper, IEnumerable objectToTrack, TrackerInfo trackerInfo)
{
componentTrackers = new Dictionary<object, IComponentTracker>();
this.helper = helper;
this.objectToTrack = objectToTrack;
this.trackerInfo = trackerInfo;
// add all children to be tracked
AddChildren(objectToTrack);
// if this list implements the collection changed event we need to listen to it
if (objectToTrack is INotifyCollectionChanged)
{
((INotifyCollectionChanged)objectToTrack).CollectionChanged += OnCollectionChanged;
}
}
/// <summary>
/// Handler for collection changed. It adds and removes trackers as the list changes
/// </summary>
/// <param name="sender"></param>
/// <param name="notifyCollectionChangedEventArgs"></param>
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
bool fireChanged = false;
bool changed = false;
if (!IsDirty)
{
fireChanged = true;
}
switch (notifyCollectionChangedEventArgs.Action)
{
case NotifyCollectionChangedAction.Add:
changed = AddChildren(notifyCollectionChangedEventArgs.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
changed = RemoveChildren(notifyCollectionChangedEventArgs.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
changed = RemoveChildren(notifyCollectionChangedEventArgs.OldItems);
changed |= AddChildren(notifyCollectionChangedEventArgs.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
changed = RemoveChildren(componentTrackers.Values.ToArray());
break;
}
if (changed)
{
isDirty = true;
}
if (isDirty && fireChanged && IsDirtyChanged != null)
{
IsDirtyChanged(this, new IsDiryChangedArgs(isDirty));
}
}
/// <summary>
/// Handler for child tracker is dirty changing
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private void ChildTrackerIsDirtyChanged(object sender, IsDiryChangedArgs eventArgs)
{
// short circuit if we are already dirty
if (isDirty)
{
return;
}
IComponentTracker childTracker = (IComponentTracker)sender;
bool newValue = childTracker.IsDirty;
bool fireChange = true;
foreach (KeyValuePair<object, IComponentTracker> kvp in componentTrackers)
{
if (kvp.Key == sender)
{
continue;
}
if (kvp.Value.IsDirty != newValue)
{
fireChange = false;
break;
}
}
if (fireChange && IsDirtyChanged != null)
{
IsDirtyChanged(this, eventArgs);
}
}
/// <summary>
/// Add children to be tracked
/// </summary>
/// <param name="childObjects"></param>
/// <returns></returns>
private bool AddChildren(IEnumerable childObjects)
{
bool returnValue = false;
IListComponentFilter listFilter = trackerInfo.ListFilter;
// if we have child tracker info create a new tracker for the object
if (trackerInfo.ChildTrackerInfo != null)
{
foreach (object child in childObjects)
{
// filter the object if a filter exists
if (listFilter != null && !listFilter.FilterComponent(child))
{
continue;
}
IComponentTracker childTracker =
helper.CreateTracker(child, trackerInfo.ChildTrackerInfo);
childTracker.IsDirtyChanged += ChildTrackerIsDirtyChanged;
componentTrackers.Add(child, childTracker);
returnValue = true;
}
}
else
{
// Create a leaf tracker for each child in the list
foreach (object child in childObjects)
{
if (listFilter != null && !listFilter.FilterComponent(child))
{
continue;
}
IComponentTracker childTracker =
helper.CreateTracker(child, new TrackerInfo { TrackerType = ComponentTrackerType.Leaf });
childTracker.IsDirtyChanged += ChildTrackerIsDirtyChanged;
componentTrackers.Add(child, childTracker);
returnValue = true;
}
}
return returnValue;
}
/// <summary>
/// Remove child object that need to stop being tracked
/// </summary>
/// <param name="childObjects"></param>
/// <returns></returns>
private bool RemoveChildren(IEnumerable childObjects)
{
bool returnValue = false;
// foreach child object try and find a tracker and dispose it
foreach (object child in childObjects)
{
IComponentTracker oldTracker;
if (componentTrackers.TryGetValue(child, out oldTracker))
{
componentTrackers.Remove(child);
oldTracker.IsDirtyChanged -= ChildTrackerIsDirtyChanged;
oldTracker.Dispose();
returnValue = true;
}
}
return returnValue;
}
NodeComponentTracker
- Tracks a specific property on an object
- The object MUST be a
INotifyCollectionChanged
implementing object
- The object MUST be a
Here are the most relevant methods:
/// <summary>
/// Initialize the new tracker
/// </summary>
/// <param name="helper"></param>
/// <param name="objectToTrack"></param>
/// <param name="trackerInfo"></param>
private void Initialize(ComponentTrackerHelper helper, object objectToTrack, TrackerInfo trackerInfo)
{
this.helper = helper;
this.objectToTrack = objectToTrack;
this.trackerInfo = trackerInfo;
// calculate if we should track the property weakly
bool trackWeakly = !(trackerInfo.PropertyType.IsValueType || trackerInfo.PropertyType == typeof(string));
object currentValue = helper.AccessProperty(objectToTrack, trackerInfo.PropertyName);
propertyTracker = new PropertyTracker(trackWeakly);
propertyTracker.SetOriginalValue(currentValue);
// if we have a current value and child tracker info start tracking
if (currentValue != null && trackerInfo.ChildTrackerInfo != null)
{
childComponentTracker = helper.CreateTracker(currentValue, trackerInfo.ChildTrackerInfo);
childComponentTracker.IsDirtyChanged += ChildComponentTrackerOnIsDirtyChanged;
}
INotifyPropertyChanged propertyChangeObject = objectToTrack as INotifyPropertyChanged;
if (propertyChangeObject != null)
{
propertyChangeObject.PropertyChanged += PropertyChangedOnTrackedObject;
}
}
/// <summary>
/// Handler for child component tracker changing is dirty
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
private void ChildComponentTrackerOnIsDirtyChanged(object sender, IsDiryChangedArgs eventArgs)
{
IComponentTracker componentTracker = sender as IComponentTracker;
if (!propertyTracker.IsDirty && IsDirtyChanged != null)
{
IsDirtyChanged(this, eventArgs);
}
}
/// <summary>
/// The property change handler for the object being tracked
/// </summary>
/// <param name="sender"></param>
/// <param name="propertyChangedEventArgs"></param>
private void PropertyChangedOnTrackedObject(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (childComponentTracker != null)
{
childComponentTracker.IsDirtyChanged -= ChildComponentTrackerOnIsDirtyChanged;
childComponentTracker.Dispose();
childComponentTracker = null;
}
bool hasOriginalValue;
object originalValue = propertyTracker.GetOriginalValue(out hasOriginalValue);
object newValue = helper.AccessProperty(objectToTrack, trackerInfo.PropertyName);
if (newValue != null)
{
if (originalValue != null &&& newValue.Equals(originalValue))
{
IsDirty = false;
}
else
{
IsDirty = true;
}
}
else
{
if (hasOriginalValue)
{
IsDirty = true;
}
else
{
IsDirty = false;
}
}
// if there is a new value and we have a child tracker info we need to create a new tracker and listen to it
if (newValue != null &&&& trackerInfo.ChildTrackerInfo != null)
{
childComponentTracker = helper.CreateTracker(newValue, trackerInfo.ChildTrackerInfo);
childComponentTracker.IsDirtyChanged += ChildComponentTrackerOnIsDirtyChanged;
}
}
Now we know there is a lot of code here, and it may be hard to take in, but have a play with the unit tests, the core API is actually very simple, and we feel this one is actually very very useful.
Convert
Expression.Convert
This simple example shows you how to use Cast (achieved using
Expression.Convert
) which takes an object and casts to type of
T
Here is the relevant code:
/// <summary>
/// Creates a new function that cast an object to a type T.
/// it will throw an exception if the cast is invalid
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Func<object, T> CreateStaticCastStatement<T>()
{
ParameterExpression inObject = Expression.Parameter(typeof(object));
// static cast expression (T)inObject
Expression convertExpression = Expression.Convert(inObject, typeof(T));
return Expression.Lambda<Func<object,T>>(convertExpression,inObject).Compile();
}
Which we could use like this. If the cast call fails an Exception
will be thrown.
Func<object,int> intStaticCastMethod = ConvertStatementCreater.CreateStaticCastStatement<int>();
int testValue = intStaticCastMethod(8);
Expression.TypeAs
We can also use Expression.TypeAs
to return null,
if the cast
fails instead of throwing an Exception
which the previous example
will do for an invalid cast.
/// <summary>
/// Creates a new function that casts an object it will return null if the cast fails
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Func<object, T> CreateAsCastStatement<T>()
{
ParameterExpression inObject = Expression.Parameter(typeof(object));
// cast using as statement inObject as T
Expression convertExpression = Expression.TypeAs(inObject, typeof(T));
return Expression.Lambda<Func<object, T>>(convertExpression, inObject).Compile();
}
Which we could use like this
Func<object, string> stringAsCastMethod = ConvertStatementCreater.CreateAsCastStatement<string>();
if (stringAsCastMethod("Hello") != "Hello")
{
throw new Exception("Cast did not work");
}
if (stringAsCastMethod(789) != null)
{
throw new Exception("Should have been null");
}
Hash Compute
In this example we kind of had a bit of fun, where we show you how you could
create a generic GetHashCode
function for any object, which is achieved by
examining all the public properties of the T
object type that is passed to the GenericHashCalculator<T>
. For each property value a new
Expression.Property
is
setup and then an Expression.Call
is done to call the GetHashCode()
method for
the property object instance. The results of which are appended to an ongoing
StringBuilder
string concatenation (where the int
value of the individual property GetHashCode()
is passed to the
StringBuilder.Append()
method). At the end of iterating all the public
properties the final StringBuilder
string, GetHashCode()
method is called which
is effectievly the overall hash value for the object.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public int Age { get; set; }
public int NumberOfSiblings { get; set; }
public int NumberOfChildren { get; set; }
}
We can build up a single properties hash value that is appended to the ongoing
StringBuilder
string value (as an int
) which will be used as the final hash value
private Expression CreatePropertyHashExpression(
PropertyInfo propertyInfo,
ParameterExpression stringBuilderParameter,
ParameterExpression objectToHashParameter)
{
Expression returnExpression = null;
Expression propertyExpression = Expression.Property(objectToHashParameter, propertyInfo);
Expression propertyHashCodeExpression = Expression.Call(propertyExpression, getHashCodeMethod);
Expression stringBuildAppendExpression = Expression.Call(
stringBuilderParameter,
stringBuilderAppendMethod,
propertyHashCodeExpression);
if (propertyInfo.PropertyType.IsValueType)
{
returnExpression = stringBuildAppendExpression;
}
else
{
Expression notEqualToNullExpress = Expression.NotEqual(propertyExpression, Expression.Constant(null));
returnExpression = Expression.IfThen(notEqualToNullExpress, stringBuildAppendExpression);
}
return returnExpression;
}
And this is the part that creates the final hash by calling the accumulated string value (from the
StringBuilder
that was used to create the indvidual property part
hashcode values) GetHashCode()
method
private void CreateHashDelegate()
{
ParameterExpression objectToHashParameter = Expression.Parameter(typeof(T), "objectToHash");
ParameterExpression stringBuilderParameter = Expression.Variable(typeof(StringBuilder), "stringBuilder");
Expression assignAndCreateExpression = Expression.Assign(stringBuilderParameter,
Expression.New(stringBuilderConstructor,
Expression.Constant(typeof(T).FullName)));
List<Expression> computeHashStatements = new List<Expression>();
computeHashStatements.Add(assignAndCreateExpression);
foreach (PropertyInfo propertyInfo in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
computeHashStatements.Add(
CreatePropertyHashExpression(propertyInfo,
stringBuilderParameter, objectToHashParameter));
}
ParameterExpression tempStringVariable = Expression.Variable(typeof(string), "tempString");
Expression toStringForBuilder = Expression.Call(stringBuilderParameter, stringBuildToStringMethod);
Expression assignToStringToTempString = Expression.Assign(tempStringVariable, toStringForBuilder);
Expression finalGetHashCodeFromString = Expression.Call(tempStringVariable, getHashCodeMethod);
computeHashStatements.Add(assignToStringToTempString);
computeHashStatements.Add(finalGetHashCodeFromString);
BlockExpression finalBlock = BlockExpression.Block(typeof(int),
new[] { stringBuilderParameter, tempStringVariable },
computeHashStatements);
hashDelegate = Expression.Lambda<Func<T, int>>(finalBlock, objectToHashParameter).Compile();
}
This is how you might use this GenericHashCalculator<T>
class
GenericHashCalculator<Person> hasher = new GenericHashCalculator<Person>();
Person testPerson1 = new Person();
Person testPerson2 = new Person();
int hashValue1 = hasher.Compute(testPerson1);
int hashValue2 = hasher.Compute(testPerson2);
testPerson1.FirstName = "Ian";
testPerson2.FirstName = "Ian";
int hashValue3 = hasher.Compute(testPerson1);
int hashValue4 = hasher.Compute(testPerson2);
if (hashValue1 != hashValue2)
{
throw new Exception("Hashes need to match");
}
if (hashValue1 == hashValue3)
{
throw new Exception("Hashes must not match");
}
if (hashValue3 != hashValue4)
{
throw new Exception("Hashes need to match");
}
testPerson1.LastName = "Johnson";
testPerson2.LastName = "IsDementedExpressionMonster";
hashValue1 = hasher.Compute(testPerson1);
hashValue2 = hasher.Compute(testPerson2);
if (hashValue1 == hashValue2)
{
throw new Exception("Hashes should not match");
}
If-Then-Else
In this example we will look at how we can construct If-Then-Else flow control using Expression trees.
In pseudo code this is what we are trying to do
.Block() { .If ($var1 > 0) { .Return returnLabel { "Positive" } } .Else { .If ($var1 < 0) { .Return returnLabel { "Negative" } } .Else { .Return returnLabel { "Zero" } } }; .Label "" .LabelTarget returnLabel:
It turns our that there is actually an Expression.IfThenElse
API
so translating this pseudo code into to an Expression API isn't that bad
actually, here is the relevant code:
public static Func<int, string> CreatePositiveNegative()
{
// Create the int parameter that is passed into the function
ParameterExpression intParameter = Expression.Parameter(typeof(int));
LabelTarget returnTarget = Expression.Label(typeof(string),"returnLabel");
// create expression that tests the int parameter is > 0
Expression testForPositive = Expression.GreaterThan(intParameter, Expression.Constant(0));
// create expression that tests the int parameter is < 0
Expression testForNegative = Expression.LessThan(intParameter, Expression.Constant(0));
// Creates a statement that returns the string "Negative" if the int
// parameter is less than 0 else it returns the string "Zero"
Expression ifNegativeElseZeroExpression =
Expression.IfThenElse(testForNegative,
Expression.Return(returnTarget, Expression.Constant(NegativeString)),
Expression.Return(returnTarget, Expression.Constant(ZeroString)));
// creates a statement that returns the string Positive if the parameter is
// greater than 0 else run the negative if/then/else expression
Expression ifPositiveElseStatement =
Expression.IfThenElse(testForPositive,
Expression.Return(returnTarget, Expression.Constant(PositiveString)),
ifNegativeElseZeroExpression);
// creates a block that contains the if statement and a return label (GoTo statement)
Expression expressionBody = Expression.Block(ifPositiveElseStatement,Expression.Label(returnTarget,Expression.Constant("")));
return Expression.Lambda<Func<int, string>>(expressionBody, intParameter).Compile();
}
Here is an example of how we would use this
Func<int, string> intTestFunc = IfThenElseStatementCreater.CreatePositiveNegative();
if (intTestFunc(1) != IfThenElseStatementCreater.PositiveString)
{
throw new Exception("Wrong value, should have been positive");
}
if (intTestFunc(-1) != IfThenElseStatementCreater.NegativeString)
{
throw new Exception("Wrong value, should have been negative");
}
if (intTestFunc(0) != IfThenElseStatementCreater.ZeroString)
{
throw new Exception("Wrong value, should have been zero");
}
We could of course replace the constant string values shown below with some more meaningful data structures
public const string PositiveString = "Positive";
public const string NegativeString = "Negative";
public const string ZeroString = "Zero";
Method Call
Methods are something that every programmer will have to call in their every
day programming existence, so it stands to reason that you may occassionally
have to create a delegate to call a method that you want to call a lot. Recall
at the beginning of this article I stated that you could do a lot of this with
Reflection, however using Reflection we take the cost of looking up a method
each time, a far better option is to create some delegate that we can use when
we need it, Ok, it still takes time to create the comiled Expression tree - to
delegate (Func<TR>
) the 1st time but after that it should be nearly
as fast as calling a delegate that was known at compile time.
We have created 2 examples of how to construct method call delegate Expression trees, both of these will make use of this object to call the methods on
public class SomeBasicClass
{
public string SomeMethod()
{
return "Hello World";
}
public string MoreComplexMethod(int param1, double param2, string param3)
{
return "Hello World " + param1 + " " + param2 + " " + param3;
}
}
Simple Example
The simple case is fairly straight forward as there are no parameters for the
example method (SomeMethod
) to deal
with, we just need to obtain a MethodInfo
an create an Expression.Lambda
to call
the correct method. Here is the relevant code for this:
protected void Initialize(MethodInfo methodInfo)
{
Type declaringType = methodInfo.DeclaringType;
// the input parameter to the function
ParameterExpression inputObject = Expression.Parameter(typeof(object));
// loval variable of type declaringType
ParameterExpression tVariable = Expression.Variable(declaringType);
// cast the input object to be declaring type
Expression castExpression = Expression.Convert(inputObject, declaringType);
// assign the cast value to the tVaraible variable
Expression assignmentExpression = Expression.Assign(tVariable, castExpression);
// call the specified method on tVariable
Expression callExpression = Expression.Call(tVariable, methodInfo);
// create a block statement that contains the body of the linq statement
BlockExpression body = Expression.Block(new[] { tVariable }, assignmentExpression, callExpression);
// create a new delegate that takes in an object and returns an object
invokeMethod = Expression.Lambda<Func<object, object>>(body, inputObject).Compile();
}
Compex Example
The complex case is a little more tricky as we need to deal with creating a
bunch of parameters for the method that will be called (MoreComplexMethod
), as such there is a bit
more code, the guts of it is pretty similar stuff though, we are just buildng up
an array of parameters that will be supplied to the final method call (Expression.Call
)
protected void Initialize(MethodInfo methodInfo)
{
// set the number of parameters so we can do a sanity check when we call it.
parameterCount = methodInfo.GetParameters().Count();
Type declaringType = methodInfo.DeclaringType;
// the parameter to call the method on
ParameterExpression inputObject = Expression.Parameter(typeof(object), "inputObject");
// the input parameters for the method
ParameterExpression parameterArray = Expression.Parameter(typeof(object[]), "parameters");
// loval variable of type declaringType
ParameterExpression tVariable = Expression.Variable(declaringType);
// keep a list of the variable we declare for use when we define the body
List<ParameterExpression> variableList = new List<ParameterExpression> { tVariable };
// cast the input object to be declaring type
Expression castExpression = Expression.Convert(inputObject, declaringType);
// assign the cast value to the tVaraible variable
Expression assignmentExpression = Expression.Assign(tVariable, castExpression);
List<Expression> bodyExpressions = new List<Expression> { assignmentExpression };
Expression callExpression = null;
if (parameterCount == 0)
{
// call the specified method on tVariable
callExpression = Expression.Call(tVariable, methodInfo);
}
else // we need to build up the parameters
{
int parameterNum = 0;
List<ParameterExpression> callArguements = new List<ParameterExpression>();
// we need to convert each parameter in the array to match the method signature
foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
{
// create new local variable of the same type as the paramete
ParameterExpression newVariable = Expression.Variable(parameterInfo.ParameterType, "param" + parameterNum);
// add it to the list of local variable we have
callArguements.Add(newVariable);
// create an expression to access the parameter array
// using a constant indexer [parameterNum]
Expression arrayAccess = Expression.ArrayAccess(parameterArray, Expression.Constant(parameterNum));
// cast the array access expression to be the parameter type
Expression castArrayValue = Expression.Convert(arrayAccess, parameterInfo.ParameterType);
// assign the casted value to the local variable
Expression variableAssign = Expression.Assign(newVariable, castArrayValue);
// add the variable assignment expression to the list of expression that is the body
bodyExpressions.Add(variableAssign);
// move to the next parameter in the array
parameterNum++;
}
// add all the new local variables to the variable list
variableList.AddRange(callArguements);
// call the method on tVaraible with the specified callArguement list
callExpression = Expression.Call(tVariable, methodInfo, callArguements);
}
// add the call expression to the body
bodyExpressions.Add(callExpression);
// create a block statement that contains the body of the linq statement
BlockExpression body = Expression.Block(variableList, bodyExpressions);
// create a new delegate that takes in an object and object array and returns an object
invokeMethod = Expression.Lambda<Func<object, object[], object>>(body, inputObject,parameterArray).Compile();
}
Property Get/Set
A very common requirement that seems to happen all the time is to get/set property values on an object. To illustrate this, we will work through a simple case, and then move on to a more complex case.
Simple-Example
This is a bulk standard property get/set where we will be using this source object
public class BusinessObject
{
public ChildObjectA A { get; set; }
public int IntProp { get; set; }
public string StringProp { get; set; }
}
Getter
So how do we code up a getter expression?
/// <summary>
/// Creates a new Func that takes an instance object and returns the property value
///
/// (System.Object)((PropertyGetSetExample.BusinessObject)$objectParam).IntProp
/// </summary>
/// <param name="propertyInfo"></param>
/// <returns></returns>
private Func<object,object> CreateGetMethod(PropertyInfo propertyInfo)
{
// create the parameter for the instance parameter of type object
ParameterExpression inParameter = Expression.Parameter(typeof(object), "objectParam");
// cast the instance to it's real type
Expression castExpression = Expression.Convert(inParameter, objectType);
// create the property access expression
Expression propertyAccessExpression =
Expression.Property(castExpression, propertyInfo);
// cast the property access expression to an object
Expression returnCastExpression = Expression.Convert(propertyAccessExpression, typeof(object));
return Expression.Lambda<Func<object, object>>(returnCastExpression, inParameter).Compile();
}
Where we would use this as shown below. This would be something like this
expressed using Reflection var x = (object)typeof(BusinessObject).GetProperty("IntProp").GetValue(bo, null);
the difference being with the Expression API
example is that it would be a compiled Func<object,object>
so would
be very quick when used again, whilst doing the same thing via Reflection would
be slow each time.
BusinessObject bo = new BusinessObject { IntProp = 80 };
PropertyInfo propertyInfo = objectType.GetProperty("IntProp",BindingFlags.Public | BindingFlags.Instance);
var getMethod = CreateGetMethod(propertyInfo);
if ((int)getMethod(bo) != 80)
{
throw new Exception("Value should have been 80");
}
Setter
So how about a setter, lets have a look at that now shall we.
/// <summary>
/// Creates a new Action that takes the instance object and the new value for the property and sets it
///
/// ((PropertyGetSetExample.BusinessObject)$objectParam).IntProp = (System.Int32)$newValue
/// </summary>
/// <param name="propertyInfo"></param>
/// <returns></returns>
private Action<object,object> CreateSetMethod(PropertyInfo propertyInfo)
{
// define the first parameter to the action the object parameter
ParameterExpression objectParameter = Expression.Parameter(typeof(object),"objectParam");
// define the new value parameter for the action
ParameterExpression newValueParameter = Expression.Parameter(typeof(object),"newValue");
// cast the instance to it's proper type
Expression castExpression = Expression.Convert(objectParameter, objectType);
// cast the new value to be the correct property type
Expression newValueCastExpression = Expression.Convert(newValueParameter, propertyInfo.PropertyType);
// create a new property access expression and assign the cast new value to it
Expression assignExpression = Expression.Assign(Expression.Property(castExpression, propertyInfo),
newValueCastExpression);
return Expression.Lambda<Action<object, object>>(assignExpression, objectParameter, newValueParameter).Compile();
}
Which you would use like this
BusinessObject bo = new BusinessObject { IntProp = 80 };
PropertyInfo propertyInfo = objectType.GetProperty("IntProp",BindingFlags.Public | BindingFlags.Instance);
var setMethod = CreateSetMethod(propertyInfo);
setMethod(bo, 160);
if ((int)getMethod(bo) != 160)
{
throw new Exception("Value should have been 160");
}
Complex-Example
There is also a more complex example, which uses these demo objects, and allows us to drill deep into an objects nested properties
public class BusinessObject
{
public ChildObjectA A { get; set; }
public int IntProp { get; set; }
public string StringProp { get; set; }
}
public class ChildObjectA
{
public ChildObjectB B { get; set; }
}
public class ChildObjectB
{
public ChildObjectC C { get; set; }
}
public class ChildObjectC
{
public int IntProp { get; set; }
public string StringPro { get; set; }
}
There is no doube that this example is a lot more complicated, as such I will
just concentrate on the getter, for nested properties. The bulk of the work is
finding and Expression that could turn something like "A.B.C.IntProp
"
into an actual Expression delegate. As you can imagine we need to recursively
examine each of these values that are seperated by a "." and create a Expression
portion that represents that part of the property tree expression that we are
trying to construct. This part is done using this code:
// <summary>
/// Creates the linq expression the goes down the dotted property chain (A.B.C)
/// It will recursively work it's way down the chain
/// </summary>
/// <param name="valueParameter">the current object in the chain</param>
/// <param name="throwParameter">if thrown parameter</param>
/// <param name="objectType">object type</param>
/// <param name="propertyName">property name (a.b.c)</param>
/// <param name="createAction">function that will be called once the final property is reached.
/// This the part that is different between get and set</param>
/// <returns></returns>
private static Expression CreateAccessPathCode(
ParameterExpression valueParameter,
ParameterExpression throwParameter,
Type objectType,
string propertyName,
Func<Type, string, Expression, Expression> createAction)
{
Expression returnExpression = null;
int firstPeriod = propertyName.IndexOf('.');
// there are more properties in the chain
if (firstPeriod > 0)
{
string currentPropertyName = propertyName.Substring(0, firstPeriod);
string theRest = propertyName.Substring(firstPeriod + 1);
Type propertyOrFieldType = GetPropertyOrFieldType(objectType, currentPropertyName);
// create local variable to hold the property we are about to inspect
ParameterExpression newValue = Expression.Variable(propertyOrFieldType);
// assign the property to the local variable
Expression assignExpression = Expression.Assign(newValue,
Expression.PropertyOrField(valueParameter, currentPropertyName));
// recurse back up the chain passing the local variable in as the starting point
// and theRest of the string for the property name
Expression recurse =
CreateAccessPathCode(newValue,throwParameter,
propertyOrFieldType,
theRest,
createAction);
// if the property type is not a value type we need to test it for null
if (!propertyOrFieldType.GetTypeInfo().IsValueType)
{
// expression to create a new Exception object with the message provided
Expression newExceptionExpression = Expression.New(
exceptionConstructor,
Expression.Constant(string.Format("Could not find property {1} on type {0}",
objectType.FullName,
currentPropertyName)));
// create a statement that will throw a new exception if the throwParameter is true
Expression throwIfMissingTrueExpression =
Expression.IfThen(Expression.IsTrue(throwParameter),
Expression.Throw(newExceptionExpression));
// create if statement that tests the local variable for null
Expression ifExpression =
Expression.IfThenElse(
Expression.NotEqual(newValue, Expression.Constant(null)),
recurse, throwIfMissingTrueExpression);
// create a new block containing the new local variable and if expression
returnExpression =
Expression.Block(new[] { newValue }, new[] { assignExpression, ifExpression });
}
else
{
// create a new block containing the recurse statement and local variable
returnExpression =
Expression.Block(new[] { newValue }, new[] { assignExpression, recurse });
}
}
else
{
// execute create action and return it.
returnExpression = createAction(objectType, propertyName, valueParameter);
}
return returnExpression;
}
The next step is the actual getter. Which looks like this.
public static GetPropertyDelegate CreateGetPropertyDelegate(Type instanceType, string propertyName, Type indexType)
{
// create input parameters for the method.
ParameterExpression valueObjectParameter = Expression.Parameter(typeof(object), "valueObject");
ParameterExpression indexParameter = Expression.Parameter(typeof(object), "index");
ParameterExpression throwParameter = Expression.Parameter(typeof(bool), "throwIfMissing");
// define a return variable at the base of
ParameterExpression returnValueExpression = Expression.Variable(typeof(object), "returnValue");
// local variable that is the proper type
ParameterExpression castValue = Expression.Variable(instanceType, "castValue");
// assign the value object to the local castValue
Expression castExpression =
Expression.Assign(castValue, Expression.Convert(valueObjectParameter, instanceType));
Expression accessBlock
= CreateAccessPathCode(castValue,
throwParameter,
instanceType,
propertyName,
(type, name, expression) =>
{
Expression returnValue = null;
if (indexType != null)
{
PropertyInfo propertyInfo = type.GetProperty(propertyName);
if (propertyInfo != null)
{
// assign the property to the returnValue using the index
returnValue = Expression.Assign(
returnValueExpression,
Expression.Convert(
Expression.Property(expression,
propertyInfo,
Expression.Convert(indexParameter, indexType)),
typeof(object)));
}
else
{
throw new Exception(
string.Format("Could not find property {0} on type {1}",
type.FullName,
name));
}
}
else
{
// assign the final property to the returnValue
returnValue = Expression.Assign(
returnValueExpression,
Expression.Convert(
Expression.PropertyOrField(expression, name), typeof(object)));
}
return returnValue;
});
// create body of method
BlockExpression returnBlock =
Expression.Block(new[] { castValue, returnValueExpression },
castExpression,
accessBlock,
returnValueExpression);
return Expression.Lambda<GetPropertyDelegate>(
returnBlock, valueObjectParameter, indexParameter, throwParameter).
Compile();
}
Granted this code is quite hairy, but when you think about what it is doing it is quite cool. It is created a delegate that allows you to create a property getter delegate against an objects nested properties, like this:
BusinessObject bo = new BusinessObject
{
A = new ChildObjectA
{
B = new ChildObjectB
{
C = new ChildObjectC { IntProp = 90 }
}
}
};var getValue =
ComplexPropertyAccessor.CreateGetPropertyDelegate(typeof(BusinessObject), "A.B.C.IntProp", null);
//returnValue will be 90
var returnValue = getValue(bo, null, true);
It can be seen that this code, actually calls the recursive method
CreateAccessPathCode
where
it passes in a callback action that is called when the recursion is over, where
the callback will be called with the final object Type
, the final
property name and the final value parameter. The callback is then used as part
of a Expression.Block that is in turn used as the final output that is used to
create a LambdaExpression
that is finally retuned to the user to
use as the property getter delegeate.
Dynamic Where
The idea behind this one is a simple one, which is a very common requirement.
We have a IEnumerable<T>
where T is some object and we wish to filter it.
However we want to build up the filter dynamically.
Say the IEnumerable<T>
was made up of Person
objetct, so we have
IEnumerable<T>
where Person
looked like this:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
If were not buiding up a filter dynamically we could simple do something like this
var x = people.Where(x => x.FirstName="sam" && LastName.Contains("Bree"));
If however we want to build up the filter at runtime based on some input criteria, such as letting the user pick which fields / methods to use in the filter, we would need to come up with a where builder. What we thought was that the where part could be considering its own class, and when we come to build up the entire dynamic where clause filter we would be combining a number of where clause part objects.
The WhereClausePart
is shown below:
public enum CompareMethod
{
Equal,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
Contains,
StartsWith,
EndsWith
}
/// <summary>
/// This class represents one part of a complex where statement
/// </summary>
public class WhereClausePart
{
public WhereClausePart()
{
}
public WhereClausePart(string propertyName, CompareMethod compareMethod, object compareValue)
{
PropertyName = propertyName;
CompareMethod = compareMethod;
CompareValue = compareValue;
}
public string PropertyName { get; set; }
public object CompareValue { get; set; }
public CompareMethod CompareMethod { get; set; }
}
Which we could then use to construct a complex dynamic where filter using 2 maiin helper methods, namely:
- CreateAndWhereClause : Which effectively ANDs all the
provided
WhereClausePart
objects together using the ExpressionAPIExpression.AndAlso
- CreateOrWereClause : Which effectively ORs all the
provided
WhereClausePart
objects together using the ExpressionAPIExpression.OrElse
That is what the bulk of the following code is doing. There are also helper
methods to do the actual comparison based on the comparison type that was
provided as part of the WhereClausePart
object(s)
public static class WhereClauseCreator
{
private static readonly MethodInfo containsMethod;
private static readonly MethodInfo endsWithMethod;
private static readonly MethodInfo startsWithMethod;
static WhereClauseCreator()
{
containsMethod = typeof(WhereClauseCreator).GetMethod("Contains",BindingFlags.NonPublic | BindingFlags.Static);
endsWithMethod = typeof(WhereClauseCreator).GetMethod("EndsWith", BindingFlags.NonPublic | BindingFlags.Static);
startsWithMethod = typeof(WhereClauseCreator).GetMethod("StartsWith", BindingFlags.NonPublic | BindingFlags.Static);
}
/// <summary>
/// Creates a Func(T,bool) that can be used as part of a Where statement in Linq
/// The comparission statements will be linked using an or operator
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="whereClause"></param>
/// <returns></returns>
public static Func<T, bool> CreateOrWhereClause<T>(IEnumerable<WhereClausePart> whereClause)
{
ParameterExpression inParameter = Expression.Parameter(typeof(T));
Expression whereStatement = null;
// foreach part of the where clause create the compare expression then or it with the current where statement
foreach (WhereClausePart part in whereClause)
{
Expression propertyEqualStatement = CompareExpression<T>(inParameter, part);
if (whereStatement == null)
{
whereStatement = propertyEqualStatement;
}
else
{
whereStatement = Expression.OrElse(whereStatement, propertyEqualStatement);
}
}
if (whereStatement == null)
{
whereStatement = Expression.Constant(true);
}
return Expression.Lambda<Func<T, bool>>(whereStatement, inParameter).Compile();
}
public static Func<T, bool> CreateAndWhereClause<T>(IEnumerable<WhereClausePart> whereClause)
{
ParameterExpression inParameter = Expression.Parameter(typeof(T));
Expression whereStatement = null;
foreach (WhereClausePart part in whereClause)
{
Expression propertyEqualStatement = CompareExpression<T>(inParameter, part);
if (whereStatement == null)
{
whereStatement = propertyEqualStatement;
}
else
{
whereStatement = Expression.AndAlso(whereStatement, propertyEqualStatement);
}
}
if (whereStatement == null)
{
whereStatement = Expression.Constant(true);
}
return Expression.Lambda<Func<T, bool>>(whereStatement, inParameter).Compile();
}
/// <summary>
/// Creates an expression where it compares a property to a particular value
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="inParameter"></param>
/// <param name="part"></param>
/// <returns></returns>
private static Expression CompareExpression<T>(ParameterExpression inParameter, WhereClausePart part)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(part.PropertyName);
// based on the comparission method we change the Expression we return back
switch (part.CompareMethod)
{
case CompareMethod.Equal:
return Expression.Equal(Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
case CompareMethod.GreaterThan:
return Expression.GreaterThan(Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
case CompareMethod.GreaterThanOrEqual:
return Expression.GreaterThanOrEqual(Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
case CompareMethod.LessThan:
return Expression.LessThan(Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
case CompareMethod.LessThanOrEqual:
return Expression.LessThanOrEqual(Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
case CompareMethod.Contains:
return Expression.Call(containsMethod,
Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
case CompareMethod.StartsWith:
return Expression.Call(startsWithMethod,
Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
case CompareMethod.EndsWith:
return Expression.Call(endsWithMethod,
Expression.Property(inParameter, propertyInfo),
Expression.Constant(part.CompareValue));
}
return null;
}
/// <summary>
/// I've created a wrapper around Contains so that I don't need to check for null in the linq expression
/// </summary>
/// <param name="testString"></param>
/// <param name="containsValue"></param>
/// <returns></returns>
private static bool Contains(string testString, string containsValue)
{
if (testString != null)
{
return testString.Contains(containsValue);
}
return false;
}
/// <summary>
/// I've created a wrapper around EndsWith so that I don't need to check for null in the linq expression
/// </summary>
/// <param name="testString"></param>
/// <param name="endsWithValue"></param>
/// <returns></returns>
private static bool EndsWith(string testString, string endsWithValue)
{
if (testString != null)
{
return testString.EndsWith(endsWithValue);
}
return false;
}
/// <summary>
/// I've created a wrapper around StartsWith so that I don't need to check for null in the linq expression
/// </summary>
/// <param name="testString"></param>
/// <param name="startsWithValue"></param>
/// <returns></returns>
private static bool StartsWith(string testString, string startsWithValue)
{
if (testString != null)
{
return testString.StartsWith(startsWithValue);
}
return false;
}
}
The actual usage of this may look something like this:
List<Person> persons = CreatePersonList();
Func<Person, bool> clause =
WhereClauseCreator.CreateAndWhereClause<Person>(new[]
{
new WhereClausePart("LastName",CompareMethod.Equal, "Doe"),
new WhereClausePart("Age",CompareMethod.GreaterThan, 20)
});
if (persons.Where(clause).Count() != 4)
{
throw new Exception("There should be only 4.");
}
Func<Person, bool> orClause =
WhereClauseCreator.CreateOrWhereClause<Person>(new[]
{
new WhereClausePart("LastName",CompareMethod.Equal, "Johnson"),
new WhereClausePart("FirstName",CompareMethod.StartsWith, "J")
});
if (persons.Where(orClause).Count() != 8)
{
throw new Exception("Result should be 8.");
}
That's It
Anayway hope you enjoyed this one. We both feel that this sort of Expression API cookbook approach can be a pretty useful way of learning more about the Expression API. I personally salute Ian for his Expression skills, which far outway my own, and we both hope that this article may prove useful to some of you. I know I have learned quite a bit just from working through Ians examples.
As always if you enjoyed the article, feel free to leave a comment/vote.