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.