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.