Download the demo code here
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 e
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.
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.
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
a IObservable<bool>
for the IsDirty
property (we were originally going to use Rx),
or raise an overall IsDirty
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.
So that was the brief for the change tracker, based on that Ian and I came up
with the following interface designs for the change tracker.
public class IsDiryChangedArgs : EventArgs
{
public IsDiryChangedArgs(bool isDirty)
{
IsDirty = isDirty;
}
public bool IsDirty { get; private set; }
}
public interface ITracker : IDisposable
{
void TrackObject<T>(T objectToTrack);
void TrackObject<T, TProp>(T objectToTrack, Expression<Func<T, TProp>> trackingExpression);
bool IsDirty { get; }
void MarkAsClean();
IEnumerable<IComponentTracker> ComponentTrackers();
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
Then examine some of the test cases, it should start to make a bit more sense.
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);
}
}
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);
}
}
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:
public interface IComponentTracker : IDisposable
{
object TrackedComponent { get; }
bool IsDirty { get; }
void MarkAsClean();
IEnumerable<IComponentTracker> ChildTrackers();
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:
public class ComponentTrackerHelper
{
private readonly Dictionary<string, Func<object, object>> accessMethods = new Dictionary<string, Func<object, object>>();
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));
}
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);
ParameterExpression inParameter = Expression.Parameter(typeof(object), "objectParam");
Expression castExpression = Expression.Convert(inParameter, target.GetType());
Expression propertyAccessExpression =
Expression.Property(castExpression, propertyInfo);
Expression returnCastExpression = Expression.Convert(propertyAccessExpression, typeof(object));
accessDelegate = Expression.Lambda<Func<object, object>>(returnCastExpression, inParameter).Compile();
accessMethods.Add(fullPropertyName, accessDelegate);
}
return accessDelegate;
}
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:
- 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
private void Initialize(ComponentTrackerHelper helper, object objectToTrack, TrackerInfo trackerInfo)
{
propertyTrackers = new Dictionary<string, PropertyTracker>();
this.helper = helper;
this.objectToTrack = objectToTrack;
this.trackerInfo = trackerInfo;
if (objectToTrack is INotifyPropertyChanged)
{
((INotifyPropertyChanged)objectToTrack).PropertyChanged += OnPropertyChanged;
}
foreach (PropertyInfo propertyInfo in objectToTrack.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (propertyInfo.CanWrite)
{
bool trackWeakly = !(propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string));
PropertyTracker propertyTracker = new PropertyTracker(trackWeakly)
object origValue = propertyTracker.PropertyAccess(objectToTrack);
propertyTracker.SetOriginalValue(origValue);
propertyTrackers[propertyInfo.Name] = propertyTracker;
}
}
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
PropertyTracker propertyTrackingInfo;
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)
{
if (newValue.Equals(originalValue))
{
propertyTrackingInfo.IsDirty = false;
dirtyChanged = true;
}
else if (!propertyTrackingInfo.IsDirty)
{
propertyTrackingInfo.IsDirty = true;
dirtyChanged = true;
}
}
else if (!hasOriginalValue)
{
if (propertyTrackingInfo.IsDirty)
{
propertyTrackingInfo.IsDirty = false;
dirtyChanged = true;
}
}
else {
if (!propertyTrackingInfo.IsDirty)
{
propertyTrackingInfo.IsDirty = true;
dirtyChanged = true;
}
}
if (dirtyChanged)
{
IsDirty = propertyTrackers.Values.Any(x => x.IsDirty);
}
}
}
- Tracks changes to a
ObservableCollection
(or any other collection that
implenents INotifyCollectionChanged
), such as
Add/Remove/Replace/Reset
- The object MUST be a
INotifyCollectionChanged
implementing
object
Here is the most relevant code:
private void Initialize(ComponentTrackerHelper helper, IEnumerable objectToTrack, TrackerInfo trackerInfo)
{
componentTrackers = new Dictionary<object, IComponentTracker>();
this.helper = helper;
this.objectToTrack = objectToTrack;
this.trackerInfo = trackerInfo;
AddChildren(objectToTrack);
if (objectToTrack is INotifyCollectionChanged)
{
((INotifyCollectionChanged)objectToTrack).CollectionChanged += OnCollectionChanged;
}
}
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));
}
}
private void ChildTrackerIsDirtyChanged(object sender, IsDiryChangedArgs eventArgs)
{
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);
}
}
private bool AddChildren(IEnumerable childObjects)
{
bool returnValue = false;
IListComponentFilter listFilter = trackerInfo.ListFilter;
if (trackerInfo.ChildTrackerInfo != null)
{
foreach (object child in childObjects)
{
if (listFilter != null && !listFilter.FilterComponent(child))
{
continue;
}
IComponentTracker childTracker =
helper.CreateTracker(child, trackerInfo.ChildTrackerInfo);
childTracker.IsDirtyChanged += ChildTrackerIsDirtyChanged;
componentTrackers.Add(child, childTracker);
returnValue = true;
}
}
else
{
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;
}
private bool RemoveChildren(IEnumerable childObjects)
{
bool returnValue = false;
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;
}
- Tracks a specific property on an object
- The object MUST be a
INotifyCollectionChanged
implementing
object
Here are the most relevant methods:
private void Initialize(ComponentTrackerHelper helper, object objectToTrack, TrackerInfo trackerInfo)
{
this.helper = helper;
this.objectToTrack = objectToTrack;
this.trackerInfo = trackerInfo;
bool trackWeakly = !(trackerInfo.PropertyType.IsValueType || trackerInfo.PropertyType == typeof(string));
object currentValue = helper.AccessProperty(objectToTrack, trackerInfo.PropertyName);
propertyTracker = new PropertyTracker(trackWeakly);
propertyTracker.SetOriginalValue(currentValue);
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;
}
}
private void ChildComponentTrackerOnIsDirtyChanged(object sender, IsDiryChangedArgs eventArgs)
{
IComponentTracker componentTracker = sender as IComponentTracker;
if (!propertyTracker.IsDirty && IsDirtyChanged != null)
{
IsDirtyChanged(this, eventArgs);
}
}
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 (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.
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:
public static Func<object, T> CreateStaticCastStatement<T>()
{
ParameterExpression inObject = Expression.Parameter(typeof(object));
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.
public static Func<object, T> CreateAsCastStatement<T>()
{
ParameterExpression inObject = Expression.Parameter(typeof(object));
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");
}
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.
So if we use this demo class
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");
}
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()
{
ParameterExpression intParameter = Expression.Parameter(typeof(int));
LabelTarget returnTarget = Expression.Label(typeof(string),"returnLabel");
Expression testForPositive = Expression.GreaterThan(intParameter, Expression.Constant(0));
Expression testForNegative = Expression.LessThan(intParameter, Expression.Constant(0));
Expression ifNegativeElseZeroExpression =
Expression.IfThenElse(testForNegative,
Expression.Return(returnTarget, Expression.Constant(NegativeString)),
Expression.Return(returnTarget, Expression.Constant(ZeroString)));
Expression ifPositiveElseStatement =
Expression.IfThenElse(testForPositive,
Expression.Return(returnTarget, Expression.Constant(PositiveString)),
ifNegativeElseZeroExpression);
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";
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;
ParameterExpression inputObject = Expression.Parameter(typeof(object));
ParameterExpression tVariable = Expression.Variable(declaringType);
Expression castExpression = Expression.Convert(inputObject, declaringType);
Expression assignmentExpression = Expression.Assign(tVariable, castExpression);
Expression callExpression = Expression.Call(tVariable, methodInfo);
BlockExpression body = Expression.Block(new[] { tVariable }, assignmentExpression, callExpression);
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)
{
parameterCount = methodInfo.GetParameters().Count();
Type declaringType = methodInfo.DeclaringType;
ParameterExpression inputObject = Expression.Parameter(typeof(object), "inputObject");
ParameterExpression parameterArray = Expression.Parameter(typeof(object[]), "parameters");
ParameterExpression tVariable = Expression.Variable(declaringType);
List<ParameterExpression> variableList = new List<ParameterExpression> { tVariable };
Expression castExpression = Expression.Convert(inputObject, declaringType);
Expression assignmentExpression = Expression.Assign(tVariable, castExpression);
List<Expression> bodyExpressions = new List<Expression> { assignmentExpression };
Expression callExpression = null;
if (parameterCount == 0)
{
callExpression = Expression.Call(tVariable, methodInfo);
}
else {
int parameterNum = 0;
List<ParameterExpression> callArguements = new List<ParameterExpression>();
foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
{
ParameterExpression newVariable = Expression.Variable(parameterInfo.ParameterType, "param" + parameterNum);
callArguements.Add(newVariable);
Expression arrayAccess = Expression.ArrayAccess(parameterArray, Expression.Constant(parameterNum));
Expression castArrayValue = Expression.Convert(arrayAccess, parameterInfo.ParameterType);
Expression variableAssign = Expression.Assign(newVariable, castArrayValue);
bodyExpressions.Add(variableAssign);
parameterNum++;
}
variableList.AddRange(callArguements);
callExpression = Expression.Call(tVariable, methodInfo, callArguements);
}
bodyExpressions.Add(callExpression);
BlockExpression body = Expression.Block(variableList, bodyExpressions);
invokeMethod = Expression.Lambda<Func<object, object[], object>>(body, inputObject,parameterArray).Compile();
}
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.
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?
private Func<object,object> CreateGetMethod(PropertyInfo propertyInfo)
{
ParameterExpression inParameter = Expression.Parameter(typeof(object), "objectParam");
Expression castExpression = Expression.Convert(inParameter, objectType);
Expression propertyAccessExpression =
Expression.Property(castExpression, propertyInfo);
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.
private Action<object,object> CreateSetMethod(PropertyInfo propertyInfo)
{
ParameterExpression objectParameter = Expression.Parameter(typeof(object),"objectParam");
ParameterExpression newValueParameter = Expression.Parameter(typeof(object),"newValue");
Expression castExpression = Expression.Convert(objectParameter, objectType);
Expression newValueCastExpression = Expression.Convert(newValueParameter, propertyInfo.PropertyType);
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");
}
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:
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('.');
if (firstPeriod > 0)
{
string currentPropertyName = propertyName.Substring(0, firstPeriod);
string theRest = propertyName.Substring(firstPeriod + 1);
Type propertyOrFieldType = GetPropertyOrFieldType(objectType, currentPropertyName);
ParameterExpression newValue = Expression.Variable(propertyOrFieldType);
Expression assignExpression = Expression.Assign(newValue,
Expression.PropertyOrField(valueParameter, currentPropertyName));
Expression recurse =
CreateAccessPathCode(newValue,throwParameter,
propertyOrFieldType,
theRest,
createAction);
if (!propertyOrFieldType.GetTypeInfo().IsValueType)
{
Expression newExceptionExpression = Expression.New(
exceptionConstructor,
Expression.Constant(string.Format("Could not find property {1} on type {0}",
objectType.FullName,
currentPropertyName)));
Expression throwIfMissingTrueExpression =
Expression.IfThen(Expression.IsTrue(throwParameter),
Expression.Throw(newExceptionExpression));
Expression ifExpression =
Expression.IfThenElse(
Expression.NotEqual(newValue, Expression.Constant(null)),
recurse, throwIfMissingTrueExpression);
returnExpression =
Expression.Block(new[] { newValue }, new[] { assignExpression, ifExpression });
}
else
{
returnExpression =
Expression.Block(new[] { newValue }, new[] { assignExpression, recurse });
}
}
else
{
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)
{
ParameterExpression valueObjectParameter = Expression.Parameter(typeof(object), "valueObject");
ParameterExpression indexParameter = Expression.Parameter(typeof(object), "index");
ParameterExpression throwParameter = Expression.Parameter(typeof(bool), "throwIfMissing");
ParameterExpression returnValueExpression = Expression.Variable(typeof(object), "returnValue");
ParameterExpression castValue = Expression.Variable(instanceType, "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)
{
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
{
returnValue = Expression.Assign(
returnValueExpression,
Expression.Convert(
Expression.PropertyOrField(expression, name), typeof(object)));
}
return returnValue;
});
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);
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.
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
}
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
ExpressionAPI Expression.AndAlso
- CreateOrWereClause : Which effectively ORs all the
provided
WhereClausePart
objects together using the
ExpressionAPI Expression.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);
}
public static Func<T, bool> CreateOrWhereClause<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.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();
}
private static Expression CompareExpression<T>(ParameterExpression inParameter, WhereClausePart part)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(part.PropertyName);
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;
}
private static bool Contains(string testString, string containsValue)
{
if (testString != null)
{
return testString.Contains(containsValue);
}
return false;
}
private static bool EndsWith(string testString, string endsWithValue)
{
if (testString != null)
{
return testString.EndsWith(endsWithValue);
}
return false;
}
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.");
}
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.