Open sandboxFocusImprove this doc

Selecting Members with Pointcuts

In a type-level or assembly-level aspect, an advice usually transforms members of the target, not the target itself. For instance, an aspect applied to a class intercepts the setters of its properties. A pointcut is a custom attribute on the advice method that selects these members.

A pointcut should select only declarations that are inside the target of the aspect. If an aspect is applied to a class A, the pointcut can select the class A and the members of A, but not other classes or their members. PostSharp enforces this rule, with the error LA0110, only for instance-scoped aspects, such as an InstanceLevelAspect. An aspect is instance-scoped when it implements IInstanceScopedAspect and is applied to a type or to an instance member. A MethodPointcut of another aspect can select a member of any type of the current assembly without error.

In an assembly-level aspect, MulticastPointcut selects nothing, and MatchPointcut causes the error LA0251. Use a MethodPointcut whose parameter is an Assembly, or add type-level aspects with IAspectProvider (see Adding Aspects Dynamically).

In a member-level aspect, such as a MethodLevelAspect, you do not need a pointcut: an advice without pointcut applies to the target of the aspect (see Developing Member-Level Aspects).

Multicast pointcut

The pointcut type MulticastPointcut selects members declaratively, with the properties of a single custom attribute. It works like MulticastAttribute (see Adding Aspects Declaratively Using Attributes): you filter the kind of declarations, their names and their attributes.

The following code applies the OnPropertySet advice to all non-abstract instance properties of the class to which the aspect is applied. The advice is generic: for each property, T is the type of the property, so the value is not boxed. The advice stores the value through the binding of the property.

[OnLocationSetValueAdvice,
 MulticastPointcut( Targets = MulticastTargets.Property,
                    Attributes = MulticastAttributes.Instance | MulticastAttributes.NonAbstract )]
public void OnPropertySet<T>( [This] object instance, [DeclarationName] string propertyName, [LocationValue] T value,
                              [Binding] ILocationBinding<T> binding )
{
    Console.WriteLine( "Setting {0} to {1}.", propertyName, value );

    binding.SetValue( instance, value );
}

When this advice is part of a type-level aspect applied to a Customer class, setting the Name and Address instance properties and the static Count property prints the following output:

Setting Name to Jane.
Setting Address to 123 Main Street.

Method pointcut

The pointcut type MethodPointcut selects members with a method of the aspect class. The argument of the custom attribute is the name of this method. Use the nameof operator, so that the compiler checks the name.

The pointcut method is an instance method of the aspect class, and it can be private. Its only parameter is of type object or of the reflection type of the declaration to which the aspect applies, such as Type or Assembly. It returns an IEnumerable<T> of the reflection type of the declarations to which the advice applies, such as PropertyInfo. Otherwise, PostSharp reports the error LA0101 or LA0102.

The following code applies the OnPropertySet advice to all writable properties that are not annotated with the IgnorePropertyChanged custom attribute.

private IEnumerable<PropertyInfo> SelectProperties( Type type )
{
    const BindingFlags bindingFlags = BindingFlags.Instance |
                                      BindingFlags.DeclaredOnly | BindingFlags.Public;

    return from property
               in type.GetProperties( bindingFlags )
           where property.CanWrite && !property.IsDefined( typeof(IgnorePropertyChangedAttribute) )
           select property;
}

[OnLocationSetValueAdvice, MethodPointcut( nameof(SelectProperties) )]
public void OnPropertySet<T>( [This] object instance, [DeclarationName] string propertyName, [LocationValue] T value,
                              [Binding] ILocationBinding<T> binding )
{
    Console.WriteLine( "Setting {0} to {1}.", propertyName, value );

    binding.SetValue( instance, value );
}

When the Address property of the target class is annotated with [IgnorePropertyChanged], setting the Name and Address properties prints the following output:

Setting Name to Jane.

Pointcut methods run at build time and can use LINQ to query System.Reflection.

Match pointcut

By default, an advice that does not fit a target causes an error. For instance, [Argument( 1 )] causes the error LA0225 on a method that has only one parameter. The MatchPointcut pointcut skips these targets instead. It requires an aspect applied to a type, such as a TypeLevelAspect. On any other target, it causes the error LA0251. It selects the methods declared by the target type that have a given name, including static and non-public methods, but not inherited methods. Each advice applies only to the methods whose signature matches its bound parameters (see Advice Parameters). Several advices can therefore handle different overloads of the same method.

When MatchParameterCount is true, which is the default value, the pointcut also requires that the number of parameters of the method is the highest index of the [Argument( index )] parameters of the advice, plus one.

Only the [Argument] parameters that select the argument by its index are counted. When the advice has none, all the methods with the name are selected.

The following aspect validates the arguments of the overloads of the Catalog.Add method. The first advice applies to Add( string ), and the second one to Add( string, string ). The method Add( int ) matches none of them, so it is left unchanged. The nameof operator names the method, so that the compiler checks the name.

[ValidateAdd]
public class Catalog
{
    public void Add( string item ) { }
    public void Add( string key, string value ) { }
    public void Add( int id ) { }
}

[PSerializable]
public sealed class ValidateAddAttribute : TypeLevelAspect
{
    [OnMethodEntryAdvice, MatchPointcut( nameof(Catalog.Add) )]
    public static void OnAddItem( [Argument( 0 )] string item )
    {
        if ( string.IsNullOrEmpty( item ) )
            throw new ArgumentException( "The item cannot be empty." );
    }

    [OnMethodEntryAdvice, MatchPointcut( nameof(Catalog.Add) )]
    public static void OnAddPair( [Argument( 0 )] string key, [Argument( 1 )] string value )
    {
        if ( string.IsNullOrEmpty( key ) )
            throw new ArgumentException( "The key cannot be empty." );
    }
}

When a program calls Add( "" ), Add( "", "value" ) and Add( 0 ), it prints the following output:

Add( "" ) failed: The item cannot be empty.
Add( "", "value" ) failed: The key cannot be empty.
Add( 0 ) succeeded.

A target is skipped when it does not have the signature of the advice, for instance when an argument is missing or has an incompatible type, or when two bound values give conflicting type arguments. An error that does not come from the signature, such as a violated generic constraint, is still reported.

By default, an advice that applies to no method causes no error, for instance when the target type has no method of the name, or when no method of the name has the signature of the advice. Set IsRequired to true to make the build fail instead, with the error LA0285, for each aspect instance whose advice applies to no method. Use it when the advice must not be lost silently, for instance after a method of the target type is renamed.

In the following example, the only Add method of Inventory has an int parameter, which does not match the string parameter of the advice:

[PSerializable]
public sealed class ValidateAddAttribute : TypeLevelAspect
{
    [OnMethodEntryAdvice, MatchPointcut( "Add", IsRequired = true )]
    public static void OnAddItem( [Argument( 0 )] string item )
    {
        if ( string.IsNullOrEmpty( item ) )
            throw new ArgumentException( "The item cannot be empty." );
    }
}

// Error LA0285: Add( int ) does not match the advice, and the class has no other Add method.
[ValidateAdd]
public class Inventory
{
    public void Add( int id ) { }
}

The build fails with the following error:

error LA0285: PointcutMatchRequired.cs(22,14): The advice "PointcutMatchRequired.ValidateAddAttribute.OnAddItem(System.String)" has a MatchPointcut with IsRequired = true, but it applies to no method of "PointcutMatchRequired.Inventory": no method is named "Add", or no method with this name has a signature that matches the parameters of the advice.

Self pointcut

The pointcut type SelfPointcut selects the target of the aspect. It is obsolete, and PostSharp ignores it: a group of advices without pointcut already applies to the target of the aspect, when its advices accept this kind of declaration. When they do not, for instance a method boundary advice in a TypeLevelAspect, PostSharp reports the error LA0049, and the advice needs a pointcut.

Remove SelfPointcut from existing aspects. Before PostSharp 2027.0, it also gave an advice a group of its own. To do that now, set the Group property (see Grouping advices).

Selecting targets programmatically

Pointcuts select the targets of the advices declared in the aspect class. PostSharp provides three ways to select targets with code instead of custom attributes:

  • MethodPointcut selects the targets of a declared advice with a method of the aspect, as shown above. Use it when the advice is known in advance and only its targets depend on the code.
  • IAdviceProvider provides, for each target of the aspect, advices that import or introduce members, such as an import of several fields of the target class into one aspect field, and requests for fields and dependencies. Use it when the advices themselves depend on the target. See Providing Advices Dynamically.
  • IAspectProvider adds other aspects to declarations of the target. Use it when you need whole aspects, possibly of different types, on members that you select with code. See Adding Aspects Dynamically.

Grouping advices

Advices of different kinds but of the same family can be grouped, so that they are woven as one transformation. For instance, the method boundary advices of a group are woven as one try / catch / finally construct, as OnMethodBoundaryAspect is.

Why group advices

Consider three advices of the method boundary family: OnMethodEntryAdvice, OnMethodExitAdvice and OnMethodExceptionAdvice. The order of these advices is important, because it results in different generated code for the try / catch / finally block.

The following two code blocks compare two results. In the first block, the advices are separate transformations, ordered as OnEntry, OnExit, OnException. In the second block, the advices are grouped.

void Method()
{
  try
  {
    OnEntry();

    try
    {
      // Original method body.
    }
    finally
    {
      OnExit();
    }
  }
  catch
  {
     OnException();
     throw;
  }
}

void Method()
{
  OnEntry();

  try
  {
      // Original method body.
  }
  catch
  {
     OnException();
     throw;
  }
  finally
  {
      OnExit();
  }
}

The code in the first block can make sense in some situations, but it is not consistent with the code generated by OnMethodBoundaryAspect. With the order OnEntry, OnException, OnExit, separate advices would generate the same code as the second block, but you would have to specify the order with custom attributes (see Ordering Advices). Grouping is the simpler way to get a consistent result.

The advices of a group can also share variables during one execution of the target method, through parameters annotated with StateAttribute (see How advices are grouped). The reasons to group the location interception advices and the event interception advices are similar: the advices of a group behave as a single transformation (see Developing Simple Aspects).

How advices are grouped

PostSharp forms the groups of each class of the aspect separately, and it infers them in most aspects:

  • An advice with a pointcut starts a group of its own. The group applies to the declarations that the pointcut selects.
  • An advice without pointcut and without the Group property joins the group of its class that has no advice of its kind yet. When the class has no such group, the advice starts a group that applies to the target of the aspect. When the class has several, PostSharp reports the error LA0230.
  • An advice with the Group property joins the group of this name. The name is shared by the classes of the aspect, so an advice of a derived class can join a group of its base class.

The inference never looks at another class, and declaration order never chooses between two groups.

The following code groups an OnMethodEntryAdvice and an OnMethodExitAdvice. OnEntry starts a group because it has a pointcut, and OnExit joins it, because the group has no exit advice yet. The stopwatch parameters of both advices are bound to the same variable: the entry advice assigns it, and the exit advice reads it.

[OnMethodEntryAdvice, MulticastPointcut]
public void OnEntry( [State( StateScope.MethodInvocation )] out Stopwatch stopwatch )
{
    stopwatch = Stopwatch.StartNew();
}

[OnMethodExitAdvice]
public void OnExit( [State( StateScope.MethodInvocation )] Stopwatch stopwatch, [DeclarationName] string methodName )
{
    Console.WriteLine( "{0} took {1} ms.", methodName, stopwatch.ElapsedMilliseconds );
}

Some properties of the advice attributes apply to the whole group: the pointcut, Description, SemanticallyAdvisedMethodKinds and UnsupportedTargetAction. They can be set on any advice of the group, but on one advice only. When two advices of a group set one of them to different values, PostSharp reports the error LA0231. When two advices of a group have a pointcut, it reports LA0259.

[OnMethodEntryAdvice( Description = "Measures the duration of the method",
                      SemanticallyAdvisedMethodKinds = SemanticallyAdvisedMethodKinds.None )]
public void OnEntry( [State( StateScope.MethodInvocation )] out Stopwatch stopwatch )
{
    stopwatch = Stopwatch.StartNew();
}

An advice dependency (AdviceDependencyAttribute) can name any advice of a group to order the whole group.

Several groups of the same family

When the class has several groups that could take an advice, set the Group property of the advices of each group to the same name. In the following example, the entry advices start two groups with their pointcuts, and the exit advice would find two groups without exit advice: Group puts it in the group of the getters.

[OnMethodEntryAdvice( Group = "Getters" ), MulticastPointcut( MemberName = "Get*" )]
public void OnGetterEntry( [State( StateScope.MethodInvocation )] out Stopwatch stopwatch )
{
    stopwatch = Stopwatch.StartNew();
}

[OnMethodExitAdvice( Group = "Getters" )]
public void OnGetterExit( [State( StateScope.MethodInvocation )] Stopwatch stopwatch )
{
    stopwatch.Stop();
    Console.WriteLine( "A getter completed." );
}

[OnMethodEntryAdvice, MulticastPointcut( MemberName = "Set*" )]
public void OnSetterEntry()
{
    Console.WriteLine( "A setter started." );
}

The Master property, which named the master advice of the group, is obsolete. It keeps working, but set Group on all the advices of the group instead.

Two groups of the same family that both apply to the target of the aspect are woven as two transformations of the same declaration, which is rarely what the author wants. PostSharp reports them with the warning LA0257.

PostSharp reports the following errors and warnings about groups:

Code Severity Meaning
LA0049 Error A group without pointcut cannot apply to the target of the aspect. Add a pointcut.
LA0059 Error The Group or Master property puts two advices of the same kind in one group.
LA0230 Error Several groups of the class have no advice of the kind of an advice. Set the Group property.
LA0231 Error Two advices of one group set a property of the group to different values.
LA0255 Warning A Group property selects the group that PostSharp infers anyway, and no derived class selects its name.
LA0257 Warning Two groups of one family apply to the target of the aspect.
LA0258 Error An advice sets both Group and Master.
LA0259 Error Two advices of one group each have a pointcut.

See Also

Reference

MulticastPointcut
MethodPointcut
MatchPointcut
SelfPointcut
Group

Other Resources

Developing Type- and Assembly-Level Aspects
Advice Methods
Advice Parameters
Providing Advices Dynamically
Adding Aspects Dynamically
Ordering Advices