Reference

You can develop an OnFieldAccess aspect by deriving the custom attribute OnFieldAccessAspect or by implementing the interface IOnFieldAccessAspect.

Overview

The OnFieldAccess aspect intercepts all read and write operations on a field. This aspect defines two handlers: OnGetValue and OnSetValue.

Both handlers receive a FieldAccessEventArgs object as an argument. This object has two interesting properties: StoredValue and ExposedValue. The StoredValue property gives access to the value that is actually stored in the field. The ExposedValue property acts like a 'hopper' between the field and the code that accesses the field. In a read operation, the ExposedValue property is the value that is actually returned to the calling code after the OnGetValue handler. In a write operation, this property stores the value to which calling code wants to set the field.

In other words, the default implementation of OnGetValue is:

eventArgs.ExposedValue = eventArgs.StoredValue

The default implementation of OnSetValue is:

eventArgs.StoredValue = eventArgs.ExposedValue

Automatic generation of properties

The problem with applying behaviors to field aspects is that these behaviors are applied only in the current assembly. Other assemblies that directly access fields would skip the aspects. Even if it is not considered as a good practice to expose fields out of the assembly, there are still cases where it is justified to do so.

PostSharp Laos does not forbid to put aspects on public fields, but the default mechanism is to wrap them into a property. In other words, if you apply an aspect to a public or protected field, this field will be made private, its name will be prefixed with a tilde (~), and a property will be generated, with the same name and type as the original field.

Note that wrapping a field into a property breaks binary compatibility of the assembly. However, source code compatibility is maintained -- you have to recompile assemblies that referenced the enhanced fields.

The default behavior can be overridden by implementing the GetOptions method of the aspect. Other behaviors is to prevent property wrapping (GenerarePropertyNever) or to force to generate a property even if the field is invisible out of the current assembly (GeneratePropertyAlways).

Removing the underlying field

The OnFieldAccess aspect gives the possibility to delete the field to which the aspect was applied. This may seem strange at first sight but makes perfectly sense in some scenarios.

A field is actually two different things: semantics (load and store) backed by a storage (a heap cell). It may happen that an aspect does not require the semantics to be backed by a heap cell. For instance, an aspect making the field transactional may will store the field value into the transaction engine, and not into the heap. For such an aspect, it makes perfectly sense to remove the field once a property, or a pair of accessor, has been generated out of it.

An aspect can require the field to be removed by implementing the GetOptions method and setting the RemoveFieldStorage flag.

Limitations of On Field Access aspects with value-typed field

We said above that aspects have two semantics: load and store. There is actually a third one: load the field address. Unfortunately, it is forbidden to load the address of a field once an aspect has been applied to it.

The reason is quite simple: once the address of a field is loaded on the stack (which is the case, for instance, when a field is passed by reference using the ref or out keyword in C#), it is possible to access the field value without using the normal "load field" and "store field" semantics. Therefore, it is possible to bypass the aspects that have been applied to this field. In order to protect the aspect from being bypassed, we had to simply forbid the address of an aspected field to be loaded.

There is however an undesirable effect when the field has a value type, for instance a decimal or a Guid. Indeed, when an instance method of a value type is invoked, the value instance (i.e. the this reference in C#) is always loaded on the stack by address. The reason is to enable instance methods to modify the current instance. Even if it is still a good idea to prevent instance methods of an aspected field to modify directly the value (therefore bypassing the aspects), it is odd to forbid any call of instance method on an aspected value-typed field... because most instance methods do not modify the value!

PostSharp Laos implements a heuristic that allows instance methods of aspected value-typed fields to be called with a limited set of value types. Indeed, we determined that all value types of the System namespace are actually immutable. However, you will probably need to circumvent this limitation for other or custom value types.

In order to call a constant method of a value type, use the Post.GetValue method. In order to call a non-constant method, you have to use a helper local variable. This is illustrated in the next code sample:

class Vehicle
{
  [Aspect] string name;
  [Aspect] Vector _position;

  public void Offset( int dx, int dy )
  {
    StylusPoint position= _position;
    position.X += dx;
    stylusPoint.Y += dy;
    _position = position;

  }

  public override string ToString() 
  {
    return string.Format( "{0}: X = {1}, Y = {2}",
                          _name,
                          Post.GetValue(_position).X, 
                          Post.GetValue(_position).Y );
  }
}

Applying many aspects to the same field

You can apply multiple aspects on the same field. Aspects are completely isolated; they have no knowledge of each other.

When multiple aspects are applied on the same field, their order is important. You can influence the aspect order by setting or implementing the AspectPriority property of the aspect.

Fields are modeled as boxes with two semantics: load and store. The OnFieldAccess aspect respect these semantics. When we apply an aspect A on a field F, we model the aspected field as two embedded boxes, a box A containing a box F.

Suppose we have three aspects A1, A2, and A3 on a field F. We represent the aspected field as four embedded boxes: A1 containing A2, which contains A3, which finally contains F. The field value is ultimately stored in box F. When some method wants to store the field value, the value has to go through all aspects before being stored. When the method wants to load the value, the value has also to go though all aspects, but in reverse order.

Therefore, when multiple aspects are applied to the same field, OnSetValue handlers are invoked in order of increasing aspect priority, but OnGetValue handlers are invoked in decreasing order.