| Severity | Warning |
| Reported by | PostSharp |
| Message | Applying the aspect "<Aspect>" to the virtual external method "<Method>" may lead to unexpected behavior. Applying OnMethodBoundaryAspect, OnExceptionAspect, or MethodInterceptionAspect to external virtual methods is not fully supported. |
Cause
An aspect can enhance the methods of a referenced assembly when you multicast it with the AttributeTargetAssemblies property, or when an IAspectProvider provides it. PostSharp does not modify the other assembly: it replaces the calls that the current assembly makes to the method with calls to a generated method that runs the aspect. With a virtual method, the implementation that runs is chosen at run time, and the calls that do not come from the current assembly, such as the calls that the library makes to its own method, do not run the aspect. PostSharp therefore warns with LA0183 when an OnMethodBoundaryAspect, an OnExceptionAspect or a MethodInterceptionAspect targets a virtual method of another assembly, and applies the aspect anyway.
<Method> is the virtual method. The message has no location in the source code.
In the following example, a billing library defines InvoiceFormatter, whose FormatTotal method is virtual:
namespace Errors.LA0183_Error.Billing;
public class InvoiceFormatter
{
public string FormatNumber( int number ) => $"Invoice #{number:00000}";
public virtual string FormatTotal( decimal amount ) => $"Total: {amount:0.00} EUR";
}
The application multicasts a [Log] aspect to the methods of InvoiceFormatter:
// @SkipPostSharpOnDependency
using System;
using Errors.LA0183_Error.Billing;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Serialization;
// The aspect is multicast to all methods of InvoiceFormatter, including the virtual FormatTotal method, which is in another assembly.
[assembly: Errors.LA0183_Error.Log(
AttributeTargetAssemblies = "LA0183_Error.Dependency",
AttributeTargetTypes = "Errors.LA0183_Error.Billing.InvoiceFormatter",
AttributeTargetElements = MulticastTargets.Method )]
namespace Errors.LA0183_Error;
[PSerializable]
public class LogAttribute : OnMethodBoundaryAspect
{
public override void OnEntry( MethodExecutionArgs args )
{
Console.WriteLine( $"Calling {args.Method.DeclaringType!.Name}.{args.Method.Name}" );
}
}
public static class Program
{
public static void Main()
{
InvoiceFormatter formatter = new();
Console.WriteLine( formatter.FormatNumber( 42 ) );
Console.WriteLine( formatter.FormatTotal( 99.5m ) );
}
}
warning LA0183: Applying the aspect "Errors.LA0183_Error.LogAttribute" to the virtual external method "Errors.LA0183_Error.Billing.InvoiceFormatter.FormatTotal(System.Decimal)" may lead to unexpected behavior. Applying OnMethodBoundaryAspect, OnExceptionAspect, or MethodInterceptionAspect to external virtual methods is not fully supported.
Because this is a warning, the program runs and logs the calls that it makes to both methods:
Calling InvoiceFormatter.FormatNumber
Invoice #00042
Calling InvoiceFormatter.FormatTotal
Total: 99.50 EUR
Resolution
- Exclude the virtual methods: set AttributeTargetExternalMemberAttributes to NonVirtual. AttributeTargetMemberAttributes does not filter the members of other assemblies.
- When you own the library, apply the aspect in the library itself, where PostSharp enhances the method itself instead of the calls.
- When the behavior at the call sites of the current assembly is what you need, keep the aspect and disable the warning with the
PostSharpDisabledMessagesMSBuild property.
In the following example, the aspect is applied to the non-virtual FormatNumber method only.
// @SkipPostSharpOnDependency
using System;
using Errors.LA0183_Fixed.Billing;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Serialization;
// The aspect is multicast to the non-virtual methods of InvoiceFormatter only.
[assembly: Errors.LA0183_Fixed.Log(
AttributeTargetAssemblies = "LA0183_Fixed.Dependency",
AttributeTargetTypes = "Errors.LA0183_Fixed.Billing.InvoiceFormatter",
AttributeTargetElements = MulticastTargets.Method,
AttributeTargetExternalMemberAttributes = MulticastAttributes.NonVirtual )]
namespace Errors.LA0183_Fixed;
[PSerializable]
public class LogAttribute : OnMethodBoundaryAspect
{
public override void OnEntry( MethodExecutionArgs args )
{
Console.WriteLine( $"Calling {args.Method.DeclaringType!.Name}.{args.Method.Name}" );
}
}
public static class Program
{
public static void Main()
{
InvoiceFormatter formatter = new();
Console.WriteLine( formatter.FormatNumber( 42 ) );
Console.WriteLine( formatter.FormatTotal( 99.5m ) );
}
}
Calling InvoiceFormatter.FormatNumber
Invoice #00042
Total: 99.50 EUR
See Also
Reference
AttributeTargetAssemblies
AttributeTargetExternalMemberAttributes
OnMethodBoundaryAspect
MethodInterceptionAspect
Other Resources