|Understanding Interception Aspects|
Aspect types MethodInterceptionAspect, LocationInterceptionAspect and EventInterceptionAspect are all based on the same principle: the aspect is invoked instead of the enhanced semantic. The aspect gets access to the intercepted semantic through methods prefixed by Proceed, or by other methods.
Things become more complex when several interception aspects are applied to the same element of code. Consider a method enhanced by three aspects A, B and C. When aspect A calls the method Proceed(), it will actually invoke the method OnInvoke(MethodInterceptionArgs) of aspect B. Similarly, aspect B will invoke aspect C, and aspect C will eventually invoke the original method.
Interception aspects form a chain on invocation where every aspect instance is a node in the chain, and the intercepted member is the last node.
An interception aspect can only invoke the next node in the chain. There is no way an aspect can invoke another node, or can access directly the intercepted member. This design ensures that aspects behave in a robust and consistent way in all situations.
Aspect types LocationInterceptionAspect and EventInterceptionAspect have several semantics (Get and Set for LocationInterceptionAspect; Add, Remove and Invoke for EventInterceptionAspect). All advices of the same aspect instance (one advice per semantic) logically belong to the same node in the chain of invocation. Therefore, when the implementation of the advice LocationInterceptionAspect.OnSetValue(LocationInterceptionArgs) invokes the method LocationInterceptionArgs.GetCurrentValue(), it actually invokes the Get semantic of the next node in the chain. If the aspect had used PropertyInfo.GetValue(Object) to get the value (as was usual in PostSharp 1.0), it would have invoked the first node in the chain!
In its early days, aspect-oriented programming (AOP) has been perceived as a dangerous technology. Aspects allowed to do anything with a program. Although AOP has been designed to improve the readability and maintainability of source code, it could actually have the opposite effect.
As goes the saying, with a sharp tool, one must pay greater attention.
PostSharp was designed to respect one of the most fundamental principles of software engineering: encapsulation. Encapsulation means the condition of being enclosed, as in a capsule. In object-oriented programming, the primary capsule is the class itself. Outside code communicates with the capsule through well-defined ports: public members. Outside code cannot modify what's inside the capsule. A well-designed capsule should check the validity of messages it receives or it sends - something called precondition and postcondition checking. The second level of encapsulation is the method: even inside a class, code should be designed so that the implementation of a method does not need to care about the implementation of another method.
Of course, it is possible to ignore the rules of encapsulation. But it would most probably result in poorly readable and maintainable code.
PostSharp actually allows you to break the first capsule: you can add advices to private members of a class. But it stops there: you cannot break the capsule of a method. Instead, you can enclose a method into a new capsule analog to a filter: the advice. When a method is enhanced by an advice, outside code seeking access to this method must go through its advice.
When a method is enhanced by several advices, every advice constitutes a filter that encloses not only the method, but all advices with lower priority.
Methods have a single semantic: Invoke. Properties, fields and events have many multiple semantics. These members can be considered as a single capsule, and their semantics as different ports in the capsule.
Things can become more complex. Consider a property with a getter and a setter. The property is enhanced by an aspect of type LocationInterceptionAspect. The property setter is enhanced by a MethodInterceptionAspect with lower priority. From a logical point of view, the property is considered as a single capsule with two ports. The capsule is enclosed by two filters, one for each aspect. The aspect LocationInterceptionAspect filters both ports. However, MethodInterceptionAspect only filters the Set port. If the LocationInterceptionAspect invokes the Get semantic, it will be directed to the property getter, because there is no filter between the advice and the semantic. However, when the same aspect invokes the Set semantic, it will be directed to LocationInterceptionAspect as this filters lays in the way.
The Invoke semantic of EventInterceptionAspect is executed in invert order. Indeed, the message originates inside the capsule is emitted outside. For all other semantics, the message always comes from outside and is directed to the capsule.
When an advice is invoked, it receives an interface to the next node in the chain of invocation: an aspect binding. Every aspect type has its corresponding binding interface, exposed on a property of the advice argument object.
Binding objects are singletons. They are fully thread-safe and reentrant. They can be invoked in any situation. This contrasts with advice arguments, which may be shared among different advices and should not be used once the advice gave over control to the next node in the chain invocation.
As objects of type Arguments may be shared among different advices, some of which may modify the arguments, it may be safe to clone the object before the advice gives over control.
For run-time performance reasons, PostSharp does not access binding classes through their interface, but directly invokes their implementation. Implementation classes of binding interfaces are considered an implementation detail and should not be referred to from user code.