In a future release of Delphi Prism later this year, we will be adding support for Aspect-Oriented Programming (AOP) through a new infrastructure we call “Cirrus”.
Delphi Prism’s AOP will make it possible to change the behavior of code, add or remove fields, properties, events or methods and even extra classes, by applying special kinds of attributes – Aspects – to classes or members.
Aspects are written in Prism itself, compiled into a separate library, and will be reusable by different projects. They will be fairly simply to write. To write an aspect, the developer would create a new class library, reference the RemObjects.Oxygene.Cirrus library, and create a new attribute class. This class should descend from System.Attribute, have a regular AttributeUsage() attribute to denote where it can be applied. The only difference from a regular attribute is that aspects implement one of the special interfaces, such as IMethodImplementationDecorator in the sample below, defined by Cirrus.
Aspect attributes will be loaded and instantiated by the compiler at compile time, and are given then chance to take very powerful influence on the code the compiler is generating.
In the example below, we are creating an aspect to decorate methods of the class it is applied to. This is done through the IMethodImplementationDecorator interface, which requires one single method, HandleImplementation to be implemented by the aspect. The compiler will call this method after a method body (implementation) for was generated and allows the aspect to take influence on the generated code and to change or augment it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | namespace ClassLibrary1;interfaceuses RemObjects.Oxygene.Cirrus;type[AttributeUsage(AttributeTargets.Classor AttributeTargets.Struct)] LogToMethodAttribute =publicclass(System.Attribute, IMethodImplementationDecorator)privateprotectedpublic[Aspect:AutoInjectIntoAspectAttribute]classmethod LogMessage(aEnter: Boolean; aName: String; Args:Arrayof object);method HandleImplementation(Services: IImplementationServices; aMethod: IMethodDefinition);end; implementation classmethod LogToMethodAttribute.LogMessage(aEnter: Boolean; aName: String; Args:Arrayof object);beginif aEnter then Console.WriteLine('Entering '+aName)else Console.WriteLine('Exiting '+aName);end; method LogToMethodAttribute.HandleImplementation(Services: IImplementationServices; aMethod: IMethodDefinition);beginif String.Equals(aMethod.Name,'LogMessage', StringComparison.OrdinalIgnoreCase)thenexit;if String.Equals(aMethod.Name,'.ctor', StringComparison.OrdinalIgnoreCase)thenexit; aMethod.SetBody(Services,methodbegin LogMessage(true, Aspects.MethodName, Aspects.GetParameters);try Aspects.OriginalBody;finally LogMessage(false, Aspects.MethodName, Aspects.GetParameters);end;end);end; end. |
The Aspects class is a special Compiler Magic Class provided by Cirrus that allows the aspect to take control of the code it is being applied to. Among other things, you see that it can query for the method name and the parameters, but also the body of the method in question, as written in the original source for the class.
By calling SetBody() on the method, the aspect can replace the body of the generated code (in this case, by taking the original body and surrounding it my our calls to LogMessage. Note how the new method body is being provided as plain, readable Prism code, in form of an extension to the anonymous method syntax.
It is also worth noting that the LogMessage method of our aspect has an aspect of their own applied. The [aspect:AutoInjectIntoClass] is an aspect defined by Cirrus itself, and it’s intended for use within aspects only. What it does is cause the member (in this case the LogMessage method to be added to the class the aspect is applied to.
This is necessary since our aspect makes use of LogMessage() in the new and augmented method body – but no such method is likely to exist in the target object. Without AutoInjectIntoClass, all the logic for LogMessage would need to be crammed into the SetBody call – making it potentially harder to read, but also potentially duplicating a lot of code and logic.
The following application makes use of our Log aspect:
namespace CirrusTest;interfaceuses ClassLibrary1, System.Linq; type[Aspect:LogToMethod] ConsoleApp =classpublicclassmethod Main;classmethod Test(S: string);end; implementation classmethod ConsoleApp.Main;begin// add your own code here Console.WriteLine('Hello World.'); Test('Input for Test');end; classmethod ConsoleApp.Test(S: string);begin Console.WriteLine('TEST: '+s);end;end. |
Running and debugging this program will output a log message at the beginning and end of each method, just as specified inour aspect was designed.
Entering Main Hello World. Entering Test TEST: Input for Test Exiting Test Exiting Main
As you can see in Reflector, the LogMessage method has also been injected into to our class, and the reference section lists only mscorlib – RemObjects.Oxygene.Cirrus.dll and our aspect .dll do not need to be deployed for the application to run. And of course the method bodies for Test and Main show the augmented calls to LogMessage:
The Cirrus library and infrastructure you are seeing here is still under development, and might still see substantial changes and refinements, API wise, until it ships. However, the above should show the general concepts of how we see this new feature being exposed to developers. We think Cirrus will be will be a useful and powerful addition to Delphi Prism’s language.