One of the big features of the May 2009 release of Delphi Prism is the introduction of support for Aspect Orientated Programming (AOP). You can read more about AOP here and Cirrus which is the AOP support in Delphi Prism.
In this blog post I'm going to implement data binding in a simple WPF application using AOP. INotifyPropertyChanged is an interface that is used to support one or two way databinding of domain objects to user interface elements. For example in a windows presentation foundation application I could bind properties of an employee object to text controls in the UI. I could then have code that updates the bound object's properties and have the UI to update as the properties are changed.
WPF supports this via the INotifyPropertyChanged interface. So taking an employee object
Employee=public class
public
property Id:Integer;
property Firstname:String;
property Lastname:String;
property Age:Integer;
end;
To implement this interface I have to declare an event called PropertyChanged and fire this each time the setter of each property is called
Showing just the Firstname property for clarity, it might look like
Employee = public class(INotifyPropertyChanged)
private
_firstname:String;
method Employee.FirePropertyChangedEvent(value:Object; fieldName:String);
public
property Firstname: read get_Firstname write set_Firstname;
event PropertyChanged: System.ComponentModel.PropertyChangedEventHandler;
end;
method Employee.get_Firstname: String;
begin
result:=self._firstname;
end;
method Employee.set_Firstname(Value:String);
begin
self._firstname:=value;
FirePropertyChangedEvent(Value, 'Firstname');
end;
method Employee.FirePropertyChangedEvent(value:Object; fieldName:String);
begin
if((assigned(value))and(assigned(self.PropertyChanged)))then
begin
self.PropertyChanged(self, new PropertyChangedEventArgs(fieldName));
end;
end;
end.
If you have domain objects with alot of properties then the lines of code can increase by quite a bit and as a result it doesn't really do much for readability.
So lets use AOP to implement the interface.
The aspect is used to decorate a class and the completed class shown below, the only difference between the first declaration and this one is a single line at the top
[aspect:NotifyPropertyChanged]
Employee = public class
public
property Id:Integer;
property Firstname:String;
property Lastname:String;
property Age:Integer;
end;
The actual magic happens at compile time and you can use reflector on the assembly to see that indeed everything is there.
The source code for this and other aspects can be found in the Delphi Prism Aspects project at the RemObjects code repository here
The implementation for the aspect can be found in the class NotifyPropertyChangedAttribute in the source file NotifyPropertyChangedAttribute.pas
The class implements the interface ITypeInterfaceDecorator.
Using my example, the method HandleInterface is called once during the construction of the Employee class.
The first part of the code seen below, adds an event, a private field to store the event and declares that the class implements INotifyPropertyChanged
var eventDefinition:=aType.AddEvent('PropertyChanged',Services.GetType('System.ComponentModel.PropertyChangedEventHandler'),false);
eventDefinition.Visibility:=Visibility.Public;
var fieldDefinition:=aType.AddField('@e_PropertyChanged',Services.GetType('System.ComponentModel.PropertyChangedEventHandler'),false);
fieldDefinition.Visibility:=Visibility.Private;
eventDefinition.SetMember(fieldDefinition,true);
aType.AddInterface(Services.GetType('System.ComponentModel.INotifyPropertyChanged'));
Its worth noting that the second parameter set to true
eventDefinition.SetMember(fieldDefinition,true);
is used to indicate that a method is introduced to raise the event is generated. This is used later on in the aspect.
The for loop in the next part of the method goes through each property and injects the raise method at the end of each setter after the original implementation. As a result when the setter is called the event is fired.
This piece of code is declaring the method call is
var fireMethod:=(eventDefinition as IEvent).RaiseMethod;
var newArg:=new NewValue(Services.GetType('System.ComponentModel.PropertyChangedEventArgs'),
[new DataValue(PropertyDefinition.Name)]);
var fireCall:=new ProcValue(new SelfValue(),fireMethod,
[new SelfValue(),newArg]);
and the code below is the new setter implementation, which is the original code plus the raise event code.
var setValue:=new ParamValue(0);
propertyDefinition.WriteMethod.SetBody(services,method
begin
Aspects.OriginalBody;
if(assigned(unquote<Object>(setValue)))then
begin
unquote(fireCall);
end;
end);
The screen shot below is from the example NotifyPropertyChanged found with with the Delphi Prism aspects project linked to above. An employee instance is bound to the UI. When the user enters a new age into the textbox in the top left hand corner and hits the change age button, the age textcontrol below is updated.
I found implementing the aspect with Cirrus certainly takes some getting used to. It's almost as if you have to channel your inner compiler and pretend your building code.
Cirrus is a very exciting addition to Delphi Prism and raises the bar against which other .Net languages have to compete. I hope to explore other uses for AOP in future blog posts.
As a reminder the source for this and other Delphi Prism Aspects and the example can be found
here.