PostSharp allows aspect not only to add interfaces to class, but also to add new methods, properties, and events. PostSharp provides a mechanism to handle situations where these members already exist in the target class. Additionally, PostSharp makes it possible to import a delegate of a target class into the aspect class, so that the aspect can invoke this member.
These mechanisms allow developers to encapsulate more design patterns using aspects.
Introducing Members
To introduce a member of the aspect class into the target type:
-
Define the method, property, or event into the aspect class as usually.
-
Ensure the member to introduce has public visibility.
-
Add the custom attribute IntroduceMemberAttribute to the member.
-
If necessary, set the following properties of the custom attribute: use Visibility to make the member private, protected, internal, or public (default value); use IsVirtual to make the member virtual (false by default).
|
|
|---|
|
Introduced members must be declared as public in the aspect class, because they will be accessed from the target class. The visibility in the target class is determined by the property Visibility. Introduced members declared can be static or non-static in the aspect class, but they are always introduced as non-static in the target class. |
The following code introduces an event into the target type of the aspect. The even must be raised from the aspect.
[IntroduceMember] public event EventHandler Changed;
Overriding Members
It may happen that the member that an aspect is trying to introduce already exists in the target class or in a parent of the target class. By default, PostSharp will emit a compile-time error if this situation happens. This behavior can be changed by setting the property OverrideAction. Valid values are Fail, OverrideOrFail or OverrideOrIgnore. The two last values means that PostSharp will try to override the member. But it is not always possible: PostSharp can only override two kinds of members: members of the target type, or virtual members of a parent of the target type. PostSharp cannot override a sealed member of a parent of the target class. In this case, OverrideAction specifies if the member introduction should fail or be silently ignored.
Importing Members
Importing a member into an aspect allows this aspect to invoke the member. An aspect can import methods, properties, or fields.
To import a member of the target type into the aspect class:
-
Define a field into the aspect class, of the following type:
Member Kind
Field Type
Method
A typed Delegate, typically one of the variants of Action or Func<(Of <(<'TResult>)>)>. The delegate signature should exactly match the signature of the imported method.
Property
Property<(Of <(<'TValue>)>)> , where the generic argument is the type of the property.
Collection Indexer
Property<(Of <(<'TValue, TIndex>)>)> , where the first generic argument is the type of the property value and the second is the type of the index parameter. Indexers with more than one parameter are not supported.
Event
Event<(Of <(<'TDelegate>)>)> , where the generic argument is the type of the event delegate (for instance EventHandler).
-
Make this field public. The field cannot be static.
-
Add the custom attribute ImportMemberAttribute to the field. As the constructor argument, pass the name of the member to be imported.
At runtime, the field is set to a delegate of the imported member. Properties and events are imported as set of delegates (Property<(Of <(<'TValue>)>)>..::..Get, Property<(Of <(<'TValue>)>)>..::..Set; Event<(Of <(<'TDelegate>)>)>..::..Add, Event<(Of <(<'TDelegate>)>)>..::..Remove). These delegates can be invoked by the aspect as any delegate.
The property ImportMemberAttribute..::..IsRequired determines what happens if the member could not be found in the target class or in its parent. By default, the field will simply have the null value if it could not be bound to a member. If the property IsRequired is set to true, a compile-time error will be emitted.
Interactions Between Several Member Introductions and Imports
Although member introduction and import may seem simple advices at first sight, things become more complex when the several advices try to introduce or import the same member. PostSharp handles these situations in a robust and predictable way. For this purpose, it is primordial to process classes, aspects and advices in a consistent order.
PostSharp enforces the following order:
-
Base classes are processed first, derived classes after. Therefore, when a class is being processed, all parent classes have already been fully processed.
-
Aspects targetting the same class are sorted (see Coping with Several Aspects on the Same Target) and executed.
-
Advices of the same aspect are sorted and executed in the following order:
-
Member imports which have the property ImportMemberAttribute..::..Order set to BeforeIntroductions.
-
Member introductions.
-
Members imports which have the property ImportMemberAttribute..::..Order set to AfterIntroductions (this is the default value).
-
Based on this well-defined order, the advices behave as follow:
|
Advice |
Precondition |
Behavior |
|---|---|---|
|
No member, or private member defined in a parent class. |
Error if ImportMemberAttribute..::..IsRequired is true, ignored otherwise (by default). |
|
|
Non-virtual member defined. |
Member imported. |
|
|
Virtual member defined. |
If ImportMemberAttribute..::..Order is BeforeIntroductions, the overridden member is imported. This similar to calling a method with the base prefix in C#. Otherwise (and by default), the member is dynamically resolved using the virtual table of the target object. |
|
|
No member, or private member defined in a parent class. |
Member introduced. |
|
|
Non-virtual member defined in a parent class |
Ignored if the property IntroduceMemberAttribute..::..OverrideAction is Ignore or OverrideOrIgnore, otherwise fail (by default). |
|
|
Virtual member defined in a parent class |
Introduce a new override method if the property IntroduceMemberAttribute..::..OverrideAction is OverrideOrFail or OverrideOrIgnore, ignore if the property is Ignore, otherwise fail (by default). |
|
|
Member defined in the target class (virtual or not) |
Fail by default or if the property IntroduceMemberAttribute..::..OverrideAction is Fail. Otherwise:
|