Prism AOP - 3
I am playing with Prism’s new Aspect Oriented Programming feature. As a learning exercise I am implementing some of the features ECO requires on a class. If all goes well I will end up with aspects I can apply to plain classes and have them run in ECO. One of those features is the ILoopBack2 interface.
In order to tackle this interface a small piece at a time I have created my own ILoopBack2 interface, so far with only one method.
This method allows the ECO to evaluate OCL (Object Constraint Language) expressions such as "self.FirstName" and have them route via the class’s property, just in case there is any logic in the getter. By implementing an interface which uses an index to identify the property to read it is possible to avoid reflection.
So, getting on with it, here is my class definition
As you can see this is a very plain class, no magic at all except for the aspect I have applied to it. And now some code which uses the class
From the example code it is evident that an instance of the Person class is castable to ILoopBack2 and also implements the GetValueByIndex method.
To achieve this the my needs to
1: Add the interface to the class
2: Generate a method
3: Implement the method.
The aspect class is a System.Attribute which implements ITypeInterfaceDecorator.
The implementation for AddILoopBack2Interface consists of the following
This initial code just does some basic checking to ensure the assembly with the interface is referenced by the target assembly (the assembly which owns the Person class). To hide the implementation of GetValueByIndex I want to make the implementing method protected, In Prism you have to explicitly link the interface method to the implementation method if it is not public. For example
Anyway, back to the important code. Here is the next part of the AddILoopBack2Interface method.
The implementation of this method will basically be
To do this we need a case statement.
Next we need to loop through each property on Person and create the case item for it.
Now I have a case statement which I have constructed dynamically I need to assign it as the body of the method which implements ILoopBack2.GetValueByIndex
The "unquote" statement tells the compiler to expand the statement into "real" code.
That’s the lot. This effectively take the following code
and compiles it as if I had written
I hope like me you see the benefits of this!
In order to tackle this interface a small piece at a time I have created my own ILoopBack2 interface, so far with only one method.
ILoopBack2 = public interface
function GetValueByIndex(I: Integer): System.Object;
end;
This method allows the ECO to evaluate OCL (Object Constraint Language) expressions such as "self.FirstName" and have them route via the class’s property, just in case there is any logic in the getter. By implementing an interface which uses an index to identify the property to read it is possible to avoid reflection.
So, getting on with it, here is my class definition
type
[aspect: EcoAspects.BusinessClass(’DomainClasses.Package1’)]
Person = public class
private
FFirstName: String;
FLastName: String;
protected
public
property FirstName : String read FFirstName write FFirstName;
property LastName : String read FLastName write FLastName;
end;
As you can see this is a very plain class, no magic at all except for the aspect I have applied to it. And now some code which uses the class
P := new Person();
P.FirstName := ’Peter’;
P.LastName := ’Morris’;
Console.WriteLine((P as ILoopBack2).GetValueByIndex(0).ToString());
Console.WriteLine((P as ILoopBack2).GetValueByIndex(1).ToString());
Output
Peter
Morris
From the example code it is evident that an instance of the Person class is castable to ILoopBack2 and also implements the GetValueByIndex method.
To achieve this the my needs to
1: Add the interface to the class
2: Generate a method
3: Implement the method.
The aspect class is a System.Attribute which implements ITypeInterfaceDecorator.
type
BusinessClassAttribute = public class(System.Attribute, ITypeInterfaceDecorator)
...
end;
procedure BusinessClassAttribute.HandleInterface(
Services: RemObjects.Oxygene.Cirrus.IServices;
aType: RemObjects.Oxygene.Cirrus.ITypeDefinition);
begin
AddClassToPackage(Services, aType);
AddILoopBack2Interface(Services, aType);
end;
The implementation for AddILoopBack2Interface consists of the following
procedure BusinessClassAttribute.AddILoopBack2Interface(Services: IServices; aType: ITypeDefinition);
var
//The type which represents ILoopBack2
ILoopBack2Type: IType;
//The type which represents the method GetValueByIndex on ILoopBack2
ILoopBack2_GetValueByIndex: IMethod;
//The new method I will create on the Person class
GetValueByIndexMethod: IMethodDefinition;
//A case statement I will use in the method
IndexCaseStatement: CaseStatement;
begin
//Find the type for ILoopBack2
ILoopBack2Type := Services.FindType(’EcoSupport.ILoopBack2’);
//If not found then we are missing a reference, report a compiler error
if (ILoopBack2Type = nil) then
begin
Services.EmitError(’EcoSupport.ILoopBack2 not found, are you missing an assembly reference?’);
exit;
end;
//Get a method reference to ILoopBack2.GetValueByIndex
ILoopBack2_GetValueByIndex := ILoopBack2Type.GetMethods(’GetValueByIndex’)[0];
This initial code just does some basic checking to ensure the assembly with the interface is referenced by the target assembly (the assembly which owns the Person class). To hide the implementation of GetValueByIndex I want to make the implementing method protected, In Prism you have to explicitly link the interface method to the implementation method if it is not public. For example
type
IMyInterface = public interface
procedure Method1();
procedure Method2();
end;
SomeClass = class(System.Object, IMyInterface)
private
//Private, so much be explicit about what it implements
procedure Method1; implements IMyInterface.Method1;
public
//Public, the link to the interface method is inferred
procedure Method2;
end;
Anyway, back to the important code. Here is the next part of the AddILoopBack2Interface method.
//Add the interface to Person
aType.AddInterface(ILoopBack2Type);
//Create a method which returns a System.Object
GetValueByIndexMethod := aType.AddMethod(’GetValueByIndex’, Services.GetType(’System.Object’), false);
//Add a parameter "I: Integer"
GetValueByIndexMethod.AddParameter(’I’, ParameterModifier.In, Services.GetType(’System.Int32’));
//Make the method protected + virtual
GetValueByIndexMethod.Virtual := VirtualMode.Virtual;
GetValueByIndexMethod.Visibility := Visibility.Protected;
//Explicitly link the method to ILoopBack2.GetValueByIndex, because the method is not public
aType.AddImplements(GetValueByIndexMethod, ILoopBack2Type, ILoopBack2_GetValueByIndex);
The implementation of this method will basically be
case I of
0: exit FirstName;
1: exit LastName;
end;
To do this we need a case statement.
//Build case statement
IndexCaseStatement := new CaseStatement();
IndexCaseStatement.What := GetValueByIndexMethod.GetParameter(’I’);
Next we need to loop through each property on Person and create the case item for it.
//The case index. We wont include array properties so this keeps
//track of how many properties we have added to the case statement
var CaseIndex: Integer := 0;
//Loop through each property declared on the type Person
for PropertyIndex : Integer := 0 to aType.PropertyCount - 1 do
begin
//Get a reference to the property
var Prop : IProperty := aType.GetProperty(PropertyIndex);
//Only continue if the property has no parameters (is not an array property)
//and only if it is possible to read this property
if (Prop.ParameterCount = 0) and (Prop.ReadMethod <> nil) then
begin
//Get the method which reads the property value
var ReadProperty : ProcValue := new ProcValue(new SelfValue(), Prop.ReadMethod);
//Construct an "exit" statement which returns the value of the property
var ExitMethod : ExitStatement := new ExitStatement(ReadProperty);
//Construct a "case item" which executes the exit statement for the
//given index
var IndexCaseItem : CaseItem := new CaseItem(ExitMethod, CaseIndex);
//Add the case item to the case statement
IndexCaseStatement.Items.Add(IndexCaseItem);
//Increment the case index for the next property added
CaseIndex := CaseIndex + 1;
end;
end;
Now I have a case statement which I have constructed dynamically I need to assign it as the body of the method which implements ILoopBack2.GetValueByIndex
GetValueByIndexMethod.SetBody(Services,
method begin
unquote(IndexCaseStatement);
end);
The "unquote" statement tells the compiler to expand the statement into "real" code.
That’s the lot. This effectively take the following code
type
[aspect: EcoAspects.BusinessClass(’DomainClasses.Package1’)]
Person = public class
private
FFirstName: String;
FLastName: String;
protected
public
property FirstName : String read FFirstName write FFirstName;
property LastName : String read FLastName write FLastName;
end;
and compiles it as if I had written
type
Person = public class(System.Object, ILoopBack2)
private
FFirstName: String;
FLastName: String;
protected
function GetValueByIndex(I: Integer) : System.Object; virtual;
public
property FirstName : String read FFirstName write FFirstName;
property LastName : String read FLastName write FLastName;
end;
implementation
function Person.GetValueByIndex(I: Integer) : System.Object;
begin
case Index of
0 : exit FirstName;
1 : exit LastName;
end;
end;
I hope like me you see the benefits of this!
Comments