Open sandboxFocusImprove this doc

PS0114: Aspects Not Ordered on the Same Declaration

Severity Warning
Reported by PostSharp
Message Conflicting aspects on "<Declaration>": transformations "<Transformation1>" and "<Transformation2>" are not commutative, but they are not strongly ordered. Their order of execution is nondeterministic.

Cause

When several aspects apply to the same declaration, PostSharp orders their transformations according to the aspect dependencies and the AspectPriority of the aspects. Two transformations commute when their order does not change the behavior, for example when a dependency with the Commute action says so. PostSharp reports PS0114 when two transformations do not commute and nothing orders them: the order that PostSharp chooses may change when you add, remove or rename an aspect, and the behavior of the program with it.

PostSharp follows the warning with an informational message that lists the dependencies of each transformation on the declaration.

In the following example, LogAttribute and RetryAttribute apply to the same method. Depending on their order, the method is logged once, or once per attempt.

using System;
using PostSharp.Aspects;
using PostSharp.Serialization;

namespace Errors.PS0114_Error;

[PSerializable]
public class LogAttribute : OnMethodBoundaryAspect // No dependency orders this aspect with respect to RetryAttribute.
{
    public override void OnEntry( MethodExecutionArgs args ) => Console.WriteLine( $"Entering {args.Method.Name}." );
}

[PSerializable]
public class RetryAttribute : MethodInterceptionAspect
{
    public override void OnInvoke( MethodInterceptionArgs args )
    {
        for ( var attempt = 1; ; attempt++ )
        {
            try
            {
                args.Proceed();

                return;
            }
            catch ( InvalidOperationException ) when ( attempt < 3 )
            {
                Console.WriteLine( $"Attempt {attempt} failed." );
            }
        }
    }
}

public class InventoryService
{
    private int calls;

    [Log]
    [Retry]
    public void Synchronize()
    {
        this.calls++;

        if ( this.calls < 2 )
        {
            throw new InvalidOperationException();
        }

        Console.WriteLine( "Synchronized." );
    }
}

public static class Program
{
    public static void Main()
    {
        new InventoryService().Synchronize();
    }
}
warning PS0114: PS0114_Error.cs(40,17): Conflicting aspects on "Errors.PS0114_Error.InventoryService.Synchronize": transformations "Errors.PS0114_Error.LogAttribute: Wrapped by advice(s) OnEntry" and "Errors.PS0114_Error.RetryAttribute: Intercepted by OnInvoke" are not commutative, but they are not strongly ordered. Their order of execution is nondeterministic.

Resolution

Decide the order of the two aspects:

In the following example, LogAttribute runs before RetryAttribute, outside of the retries, so the method is logged once.

using System;
using PostSharp.Aspects;
using PostSharp.Aspects.Dependencies;
using PostSharp.Serialization;

namespace Errors.PS0114_Fixed;

[PSerializable]
[AspectTypeDependency( AspectDependencyAction.Order, AspectDependencyPosition.Before, typeof(RetryAttribute) )] // Logs once, outside of the retries.
public class LogAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry( MethodExecutionArgs args ) => Console.WriteLine( $"Entering {args.Method.Name}." );
}

[PSerializable]
public class RetryAttribute : MethodInterceptionAspect
{
    public override void OnInvoke( MethodInterceptionArgs args )
    {
        for ( var attempt = 1; ; attempt++ )
        {
            try
            {
                args.Proceed();

                return;
            }
            catch ( InvalidOperationException ) when ( attempt < 3 )
            {
                Console.WriteLine( $"Attempt {attempt} failed." );
            }
        }
    }
}

public class InventoryService
{
    private int calls;

    [Log]
    [Retry]
    public void Synchronize()
    {
        this.calls++;

        if ( this.calls < 2 )
        {
            throw new InvalidOperationException();
        }

        Console.WriteLine( "Synchronized." );
    }
}

public static class Program
{
    public static void Main()
    {
        new InventoryService().Synchronize();
    }
}
Entering Synchronize.
Attempt 1 failed.
Synchronized.

See Also

Reference

AspectTypeDependencyAttribute
AspectDependencyAction
AspectPriority

Other Resources

Coping with Several Aspects on the Same Target
Ordering Advices