Open sandboxFocusImprove this doc

Providing Advices Dynamically

An aspect usually declares its import and introduction advices with custom attributes, such as ImportMemberAttribute, IntroduceMemberAttribute and IntroduceInterfaceAttribute (see Introducing Interfaces, Methods, Properties and Events into Existing Classes and Accessing Members of the Target Class). A custom attribute is the same for every target. When the advices depend on the target, for instance when the aspect must import every field of a given type, the aspect implements the IAdviceProvider interface instead.

PostSharp calls the ProvideAdvices(object) method at build time, for each target of the aspect. The method receives the target as a reflection object, typically a Type, and returns the advices to add to the aspect instance, as AdviceInstance objects.

Advice instances

The following classes represent the advices that an aspect can provide:

Class Equivalent attribute Advice
ImportLocationAdviceInstance ImportMemberAttribute on a field of type Property<TValue> Imports a field or property of the target class into an aspect field of type ILocationBinding, or of a collection type such as List<ILocationBinding>, with one advice per member. A property can also be imported into a Property<TValue> field, as with the attribute, but a field cannot.
ImportMethodAdviceInstance ImportMemberAttribute on a field of a delegate type Imports a method of the target class into a delegate field of the aspect.
IntroduceMethodAdviceInstance IntroduceMemberAttribute Introduces a public method of the aspect class into the target class.
IntroduceInterfaceAdviceInstance IntroduceInterfaceAttribute Introduces an interface, which the aspect class implements, into the target class.

Import advices and interface introductions require an aspect derived from InstanceLevelAspect (errors LA0079 and LA0069). The aspect fields and methods that these advices refer to are public members of the aspect class, as with the custom attributes, and the import fields are instance fields. An aspect field that receives imported members is set at run time, so mark it with PNonSerializedAttribute when the aspect is serialized with PSerializableAttribute.

Behaviors such as method boundary and interception advices cannot be provided this way. To apply them to members selected with code, use a MethodPointcut (see Selecting Members with Pointcuts), or add aspects with IAspectProvider (see Adding Aspects Dynamically).

Example

The following aspect implements IDisposable on its target class, and disposes all the fields of the class whose type implements IDisposable. ProvideAdvices imports each of these fields into the DisposableFields list, and introduces the interface only when the class has such fields.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Reflection;
using PostSharp.Serialization;

[PSerializable]
public sealed class DisposeFieldsAttribute : InstanceLevelAspect, IAdviceProvider, IDisposable
{
    [PNonSerialized]
    public List<ILocationBinding> DisposableFields;

    public IEnumerable<AdviceInstance> ProvideAdvices( object targetElement )
    {
        Type targetType = (Type) targetElement;
        FieldInfo aspectField = typeof(DisposeFieldsAttribute).GetField( nameof(DisposableFields) );

        List<FieldInfo> fields = targetType
            .GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly )
            .Where( field => typeof(IDisposable).IsAssignableFrom( field.FieldType ) )
            .ToList();

        foreach ( FieldInfo field in fields )
        {
            yield return new ImportLocationAdviceInstance( aspectField, new LocationInfo( field ) );
        }

        if ( fields.Count > 0 )
        {
            yield return new IntroduceInterfaceAdviceInstance( typeof(IDisposable), InterfaceOverrideAction.Ignore );
        }
    }

    public void Dispose()
    {
        foreach ( ILocationBinding binding in this.DisposableFields )
        {
            ((IDisposable) binding.GetValue( this.Instance ))?.Dispose();
        }
    }
}

With InterfaceOverrideAction.Ignore, a class that already implements IDisposable, for instance through a base class, keeps its own Dispose method, and the aspect does not dispose its fields.

Extension blocks

ProvideAdvices receives reflection objects, which represent the code that the compiler generates. For C# 14 extension blocks, this code includes metadata types and members that do not exist in the source code. Do not select them: use IsExtensionBlockMetadata to recognize them (see C# 14 Extension Blocks and Multicasting).

See Also

Reference

IAdviceProvider
AdviceInstance
ImportLocationAdviceInstance
ImportMethodAdviceInstance
IntroduceMethodAdviceInstance
IntroduceInterfaceAdviceInstance

Other Resources

Developing Type- and Assembly-Level Aspects
Selecting Members with Pointcuts
Introducing Interfaces, Methods, Properties and Events into Existing Classes
Accessing Members of the Target Class
Adding Aspects Dynamically