Monday, 27 October 2008

Using C# Automatic Properties with XPO Objects - Part 2

In part 1 we looked at how XPO implements change tracking by utilising some magic from the base XPObject class.  An XPO object looked something like this:

public class Customer: XPObject
  private DateTime date;
  public DateTime Date
    get { return date; }
    set { SetPropertyValue("Date", ref date, value); }

  [Association("CustomerOrders", typeof(Order))]
  public XPCollection<Order> Order
    get { return GetCollection<Order>("Orders"); } 

Wanting to cut down on the syntatic noise, I'd like to be able to use C# automatic properties to make my classes to look like this:

public class Customer: XPObject
  public DateTime Date { get; set; }

  [Association("CustomerOrders", typeof(Order))]
  public XPCollection<Order> Order
    get; private set; 

The secret here is the XpoAutomaticProperties attribute and the work that this does.  Applying this attribute to the class makes two things happen:
  • Changing a property value will raise the changed notification events.
  • Accessing a collection will instantiate and set up the collection.
XPO objects notify observers of changesto values using a changed event.  Collections have similar events events are fired when the collection changes or when an item in the collection changes.  Note also that the GetCollection method above sets up the collection so that it is aware of being a property of a containing object.  This will also be handled by the attribute.

Along with the XpoAutomaticProperties attribute, there is one other thing that is introduced.  The SetPropertyValue call is responsible for firing the changed event.  I will introduce to allow me to call back to an object to instruct it to notify observers of changes (XPObject has exactly such a method).  The interface looks like this:

public interface IPropertyChangedNotifier
  void OnChanged(
    string propertyName, 
    object oldValue, 
    object newValue);

Okay, if you've got this far, you must be wondering how do we actually do this?  The answer is that we use the incredible PostSharp to help us add the required code we need into our objects.  PostSharp is an aspect oriented programming (AOP) library and toolset that makes it easy to add code into your objects during compilation.  As the changes occur at compile time, you avoid the runtime performance hits that would come from using reflection.

PostSharp makes the process of generating and adding IL code to your assemblies at compile time ridiculously easy to acheive for such a complex task.  Although PostSharp allows you to drop down and weave any IL code you like into your assemblies, there is a  The PostSharp website has a number of excellent examples of how to work with PostSharp, which are extremely impressive and worth checking out.  It is also very well documented.  PostSharp is an open source library that is free even for commercial use and, incredibly, is the work of one man: Gael Fraiteur.  Everything that we do here is based on the excellent examples that Gael provides with PostSharp, so check those out when you can.

From this point on, I am going to assume that you have some knowledge of AOP and PostSharp.  So, back to the XpoAutomaticProperties attribute and how this works.  The attribute is a PostSharp attribute in which you override a base method to do the work.  The method looks like:

public override void ProvideAspects(
  object targetElement, 
  LaosReflectionAspectCollection collection)

Inside this method you use reflection to interrogate the properties of the type and then add some code to that property like this:

1 if (property.CanWrite && property.CanRead && !property.IsCollection())
2 {
3   var setMethod = property.GetSetMethod(true);
4   if (!setMethod.IsStatic)
5   {
6      var aspect = new PropertyChangedNotifierSubAspect(
                    this, property.Name);
7      collection.AddAspect(setMethod, aspect);
8   }
9 }

Remember that all this code is firing at compile time so the reflection will not affect your runtime performance!  So, in line 1 we check that the property (a PropertyInfo) is read-write and is not a collection.  We then use reflection to access the property's setter in line 3 and check that we are not looking at a static property in line 4.  In line 6 we create a new PostSharp aspect - the PropertyChangedNotifierSubAspect - and add it to the collection that PostSharp supplies.  Note that the name of the property is passed in to the aspect's constructor - this is how we avoid the use of string literals for property names.  After this, PostSharp does all the work for you.  Well, PostSharp does the IL weaving for you, there's still a little more for us to do.

The PropertyChangedNotifierSubAspect decends from the built in PostSharp type OnMethodBoundaryAspect. This allows you to easily acheive a number of things when a method is called in your code. We use two of these to change the property's setter: first we store the old value when the setter is entered, then when the setter method completes we determine if the value has changed and fire the changed event accordingly.  PostSharp elegantly provides us methods that we can override to achieve all this - we never even have to leave C# or think about IL.  This is how we store the existing value when we enter the property's setter:

1 public override void OnEntry(MethodExecutionEventArgs eventArgs)
2 {
3   base.OnEntry(eventArgs);
4   var instance = (XPBaseObject) eventArgs.Instance;
5   oldValue = instance.GetMemberValue(propertyName);
6 }

We simply override the base class method and use the data supplied argument's to access the object whose property has changed.  We cast it to be an XPBaseObject and use the GetMemberValue to access and store the value of the property on entry.  Note here that the GetMemberValue is supplied by Developer Express and is performance efficient (itself using generated IL to access the property value).  If you are using XPO with your own objects that do not subclass the built in XPO types then you will have to use reflection or some other means to access the entry value.  Note also that this code is executes at runtime.

In a similar manner, we then override the OnSuccess method to execute code when the method successfully completes (i.e. exiting with no unhandled exceptions).  At this stage, we compare the current value with the existing and fire the changed event if needed.  The code looks like: 

1 public override void OnSuccess(MethodExecutionEventArgs eventArgs)
2 {
3    if (!(eventArgs.Instance is IPropertyChangedNotifier)) return;
4    var newValue = eventArgs.GetReadOnlyArgumentArray()[0];
5    if (ValuesEqual(oldValue, newValue)) return;
6    var instance = (IPropertyChangedNotifier) eventArgs.Instance;
7    instance.OnChanged(propertyName, oldValue, newValue);
8 }

 Again, PostSharp supplies us with event args that allow easy access to values.  We also use our IPropertyChangedNotifier interface to do the notification.  In line 4 we access the new property value from the event arguments supplied by PostSharp (again, no reflection needed!) then call a method to check for changes in line 5.  If we have a change to the property, we instruct the object to notify the world of the change in lines 6 and 7. 

Okay, so where are we now?  We've used PostSharp to create a special attribute.  We've applied the attribute to the classes in which we want to use automatic properties.  PostSharp recognises our attribute and injects code into our assemblies at compile time.  The code that is injected is defined by us in classes that we create from buit in PostSharp types.  These types are called aspects and we used the one that allows us to modify a method - specifically, the property's setter method.  To modify the method was pretty easy - we just override a couple of methods and use the information that PostSharp passes back to us to do what we want.

PostShapr also allows you to work with fields and add code to when the fields are accessed.  The way I initialise collections use this feature of PostSharp, but I'd suggest you head for the PostSharp site to learn more of what you can do with PostSharp.

Before we leave this, there's one gotcha to be aware of: the release version of PostSharp, 1.0, does not play well in partial trust scenarios.  If this is important to you then you'll be pleased to know that the next release 1.5 addresses this and version 1.5 is in CTP2 stage at time of writing.

If you want more, then you can get the code from here.


Rinat Abdullin said...

Nice approach of working around some flexibility limitations of XPO.

Added your posts to favs, in case I'll have to do some desktop development with the embedded databases (which will probably happen soon)

Sean Kearon said...

Thanks Rinat!