Open sandboxFocusImprove this doc

LA0163: Instance-Scoped Aspect Applied to a Declaration of Another Assembly

Severity Error
Reported by PostSharp
Message Cannot apply the instance-scoped aspect "<Aspect>" to "<Target>" because this declaration is in module "<Module>", not in the module being processed. Apply the aspect to a declaration of the current module instead, for example to an override of the method.

Cause

An instance-scoped aspect, which implements IInstanceScopedAspect, has one aspect instance for each instance of the target class, created when the class is instantiated. PostSharp adds the code that creates and stores these aspect instances to the class that declares the target. It can only modify the assembly that it is processing, so it cannot apply an instance-scoped aspect to a member declared in another assembly, <Module>.

A custom attribute cannot be placed on a member of another assembly, so the target usually comes from an IAspectProvider: reflection methods such as Type.GetMethod also return the members that a class inherits, including those of a base class defined in a library.

In the following example, the [TrackCommand] aspect provider gives an instance-scoped [CountExecutions] aspect to the Execute method of SaveCommand. SaveCommand does not override Execute, so GetMethod returns the method of CommandBase, which is defined in a library.

// @DisabledMessages(PS0131)

using System;

namespace Errors.LA0163_Error.Commands;

public abstract class CommandBase
{
    public virtual void Execute() => Console.WriteLine( $"Executing {this.GetType().Name}." );
}
using System;
using System.Collections.Generic;
using System.Reflection;
using Errors.LA0163_Error.Commands;
using PostSharp.Aspects;
using PostSharp.Serialization;

namespace Errors.LA0163_Error;

// An instance-scoped aspect: each command object gets its own aspect instance, which counts its executions.
[PSerializable]
public class CountExecutionsAttribute : MethodInterceptionAspect, IInstanceScopedAspect
{
    [PNonSerialized]
    private int _count;

    public object CreateInstance( AdviceArgs adviceArgs ) => this.MemberwiseClone();

    public void RuntimeInitializeInstance()
    {
    }

    public override void OnInvoke( MethodInterceptionArgs args )
    {
        this._count++;
        Console.WriteLine( $"Execution {this._count} of {args.Instance!.GetType().Name}." );
        args.Proceed();
    }
}

[PSerializable]
public class TrackCommandAttribute : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects( object targetElement )
    {
        MethodInfo execute = ((Type) targetElement).GetMethod( nameof(CommandBase.Execute) )!;

        yield return new AspectInstance( execute, new CountExecutionsAttribute() );
    }
}

// SaveCommand inherits Execute from CommandBase, which is defined in another assembly.
[TrackCommand]
public class SaveCommand : CommandBase
{
}

public static class Program
{
    public static void Main()
    {
        SaveCommand command = new();
        command.Execute();
        command.Execute();
    }
}

PostSharp reports the error on the declaration of the target member, in the source of the library:

error LA0163: LA0163_Error.Dependency.cs(9,25): Cannot apply the instance-scoped aspect "Errors.LA0163_Error.CountExecutionsAttribute" to "Errors.LA0163_Error.Commands.CommandBase.Execute" because this declaration is in module "LA0163_Error.Dependency.dll", not in the module being processed. Apply the aspect to a declaration of the current module instead, for example to an override of the method.

Resolution

  • Override the member in a class of the current project, and apply the aspect to the override. A reflection method such as Type.GetMethod then returns the override.
  • In an aspect provider, skip the members of other assemblies, for example by comparing the Module of the member with the Module of the target type, or by passing BindingFlags.DeclaredOnly to the reflection method.
  • If the aspect does not need state for each instance, do not implement IInstanceScopedAspect. PostSharp can then apply some aspects, such as a MethodInterceptionAspect, to a method of another assembly by modifying the calls to the method in the current assembly. For a virtual method, PostSharp warns that this is not fully supported (LA0183).

In the following example, SaveCommand overrides Execute, so each command counts its own executions.

using System;
using System.Collections.Generic;
using System.Reflection;
using Errors.LA0163_Fixed.Commands;
using PostSharp.Aspects;
using PostSharp.Serialization;

namespace Errors.LA0163_Fixed;

// An instance-scoped aspect: each command object gets its own aspect instance, which counts its executions.
[PSerializable]
public class CountExecutionsAttribute : MethodInterceptionAspect, IInstanceScopedAspect
{
    [PNonSerialized]
    private int _count;

    public object CreateInstance( AdviceArgs adviceArgs ) => this.MemberwiseClone();

    public void RuntimeInitializeInstance()
    {
    }

    public override void OnInvoke( MethodInterceptionArgs args )
    {
        this._count++;
        Console.WriteLine( $"Execution {this._count} of {args.Instance!.GetType().Name}." );
        args.Proceed();
    }
}

[PSerializable]
public class TrackCommandAttribute : TypeLevelAspect, IAspectProvider
{
    public IEnumerable<AspectInstance> ProvideAspects( object targetElement )
    {
        MethodInfo execute = ((Type) targetElement).GetMethod( nameof(CommandBase.Execute) )!;

        yield return new AspectInstance( execute, new CountExecutionsAttribute() );
    }
}

[TrackCommand]
public class SaveCommand : CommandBase
{
    // SaveCommand overrides Execute, so the aspect is applied to a method of the current project.
    public override void Execute() => base.Execute();
}

public static class Program
{
    public static void Main()
    {
        SaveCommand command = new();
        command.Execute();
        command.Execute();
    }
}
Execution 1 of SaveCommand.
Executing SaveCommand.
Execution 2 of SaveCommand.
Executing SaveCommand.

See Also

Reference

IInstanceScopedAspect
IAspectProvider

Other Resources

Understanding Aspect Lifetime and Scope
Adding Aspects Programmatically Using IAspectProvider