| Severity | Warning |
| Reported by | PostSharp |
| Message | The advice "<Advice>" has both a MethodExecutionArgs parameter and the [Exception] parameter "<Parameter>", passed as out or ref. With FlowBehavior.ThrowException, the value of the [Exception] parameter is thrown, and MethodExecutionArgs.Exception is ignored. |
Cause
In an OnMethodExceptionAdvice, a parameter annotated with ExceptionAttribute and passed as out or ref is the variable that holds the caught exception. When the advice sets FlowBehavior.ThrowException, PostSharp throws the value of this variable.
When the same advice also has a MethodExecutionArgs parameter, the advice can set the exception to throw in a second place, the Exception property, which PostSharp then ignores. PostSharp reports LA0248 on the [Exception] parameter as soon as the advice has both parameters, whether or not it sets the property, so that a replacement written to the property is not lost silently.
<Advice> is the advice method, with its parameter types, and <Parameter> is the name of the [Exception] parameter.
In the following example, the [WrapException] aspect wraps the exceptions of Save into a DataAccessException. Its advice receives the exception through a ref parameter, and the method name through MethodExecutionArgs.
using System;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Serialization;
namespace Errors.LA0248_Error;
public class DataAccessException : Exception
{
public DataAccessException( string message, Exception innerException ) : base( message, innerException )
{
}
}
[PSerializable]
public class WrapExceptionAttribute : MethodLevelAspect
{
[OnMethodExceptionAdvice]
// The advice has both a MethodExecutionArgs parameter and a ref [Exception] parameter.
public void OnException( MethodExecutionArgs args, [Exception] ref Exception exception )
{
exception = new DataAccessException( $"{args.Method.Name} failed.", exception );
args.FlowBehavior = FlowBehavior.ThrowException;
}
}
public class OrderRepository
{
[WrapException]
public void Save( int orderId ) => throw new InvalidOperationException( $"The connection is closed, so order {orderId} is not saved." );
}
public static class Program
{
public static void Main()
{
try
{
new OrderRepository().Save( 42 );
}
catch ( DataAccessException e )
{
Console.WriteLine( $"{e.Message} {e.InnerException!.Message}" );
}
}
}
warning LA0248: LA0248_Error.cs(20,82): The advice "Errors.LA0248_Error.WrapExceptionAttribute.OnException(PostSharp.Aspects.MethodExecutionArgs, System.Exception@)" has both a MethodExecutionArgs parameter and the [Exception] parameter "exception", passed as out or ref. With FlowBehavior.ThrowException, the value of the [Exception] parameter is thrown, and MethodExecutionArgs.Exception is ignored.
Resolution
Read and replace the exception in one place only:
- Remove the
[Exception]parameter, and read and set Exception. WithFlowBehavior.ThrowException, PostSharp then throws the value of the property. - Or remove the MethodExecutionArgs parameter, and bind the other values that the advice needs with their own parameters: FlowBehaviorAttribute for the flow behavior, DeclarationAttribute for the target method.
In the following example, the advice reads and replaces the exception through MethodExecutionArgs only.
using System;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Serialization;
namespace Errors.LA0248_Fixed;
public class DataAccessException : Exception
{
public DataAccessException( string message, Exception innerException ) : base( message, innerException )
{
}
}
[PSerializable]
public class WrapExceptionAttribute : MethodLevelAspect
{
[OnMethodExceptionAdvice]
// The advice reads and replaces the exception only through MethodExecutionArgs.
public void OnException( MethodExecutionArgs args )
{
args.Exception = new DataAccessException( $"{args.Method.Name} failed.", args.Exception );
args.FlowBehavior = FlowBehavior.ThrowException;
}
}
public class OrderRepository
{
[WrapException]
public void Save( int orderId ) => throw new InvalidOperationException( $"The connection is closed, so order {orderId} is not saved." );
}
public static class Program
{
public static void Main()
{
try
{
new OrderRepository().Save( 42 );
}
catch ( DataAccessException e )
{
Console.WriteLine( $"{e.Message} {e.InnerException!.Message}" );
}
}
}
Save failed. The connection is closed, so order 42 is not saved.
See Also
Reference
ExceptionAttribute
OnMethodExceptionAdvice
MethodExecutionArgs
FlowBehavior
Other Resources