Overview
A striking characteristics of PostSharp Laos is that aspects are instantiated at compile time. Therefore, aspects have a complex lifetime.
It is important to understand that persistence of aspects between compile time and run time is achieved by serializing aspect instances into a binary resourced stored in the transformed assembly. Therefore, you should carefully mark all aspect classes as serializable, and distinguish between serialized fields (typically initialized at compile-time) and non-serialized fields (typically used at run time only).
1. At compile time
1.1. Instantiation of aspects
PostSharp Laos creates a new instance of the aspect for every target to which it applies. If the aspect has been applied using a multicast custom attribute, there will actually be one aspect instance for each matching type, field or method.
When the aspect is given as a custom attribute or a multicast
custom attribute, each custom attribute instance is instantiated
using the same mechanism as the Common Language Runtime (CLR) does:
PostSharp Laos calls the proper constructor and sets the properties
and/or fields with the proper values. For instance, when you
use the [Trace(Category="FileManager")] construction,
PostSharp calls the default constructor and the
Category property setter.
Note. The constructors and property setters should store any piece information in serializable fields. The aspect class itself, and all its ancestors, should be marked as serializable as well. This is because, at run time, we will not construct the custom attribute instance using the constructor and fields/properties, but we will deserialize it.
1.2. Compile-time initialization
Here we are still at compile-time, during the build process, inside the PostSharp post-compiler. We have an instance of the custom attribute, but this instance does not know yet to which element (type, method, field, ...) it is associated.
At this moment, PostSharp Laos invokes the
CompileTimeInitialize method. This method may, but
must not, be overridden by concrete aspect classes in order to
perform some expensive computations that do not depend on runtime
conditions. The name of the element to which the custom attribute
instance is applied is always passed to this method.
For instance, when implementing tracing, it is relatively costly
to build the string representing the complete class and method
name. However, it is something that perfectly be computed at
compile-time and reused at runtime. This is possible by
implementing the CompileTimeInitialize and storing the
tracing string in a serializable field.
Note that the implementation currently cannot serialize the
Type, FieldInfo or
MethodBase. The reason is that, at compile-time, you
receive PostSharp-specific implementations of these model metadata
elements. That is, at runtime, you could not deserialize them into
runtime elements (like RuntimeType,
RuntimeFieldInfo, ...).
1.3. Serialization of custom attribute instances
After the custom attributes instances have all been created and initialized, PostSharp Laos serialize them into a binary stream. This stream is stored inside the new assembly as a managed resource.
2. At runtime
2.1. Deserialization of custom attributes
This step and the following occur only once at the beginning of the program execution (precisely: the first time any aspect is 'hit'). They are implemented in the static constructor of the 'secrete' class generated during compilation.
This static constructor first deserializes the binary stream that has been stored in a managed resource during post-compilation. All serialized fields set up before are restored.
2.2. Runtime initialization
Once all custom attribute instances are deserialized, we call
for each of them the RuntimeInitialize method. But
this time we pass as an argument the real runtime object
(Type, FieldInfo or
MethodBase) to which it is applied. If the aspect
class needs this object, it is the time to store it in an instance
field. But remember that this field cannot be serializable! Use the
NonSerialized custom attribute for this purpose.
2.3. Invocation of interception methods
What we name here 'interception methods' are the methods that
should intercept some execution points, for instance the
OnException method of OnExceptionAspect
or GetValue of OnFieldAccessAspect.
Interceptions methods are invoked as many times as what they intercept is invoke. These methods should be designed to be efficient, especially when they are to be applied on time-critical methods.