| 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:
- When the aspect classes are yours, add a dependency to one of them: an AspectTypeDependencyAttribute with the Order action, or an AspectRoleDependencyAttribute with a role that the other aspect provides with ProvideAspectRoleAttribute.
- When you only use the aspects, set the AspectPriority property of both aspects where you apply them.
- When the order does not matter, add a dependency with the Commute action.
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