| Severity | Error |
| Reported by | PostSharp |
| Message | The parameter "<Parameter>" of the advice "<Advice>" is bound by [ImportMember] and passed as out or ref to the field "<Field>", but an aspect turns this field into a property, which has no address. Pass the parameter by value or as in, or import the field as ILocationBinding<T>. |
Cause
An advice parameter annotated with ImportMemberAttribute and passed as ref or out receives a reference to a field of the target type, so that the advice can change the field. When another aspect, such as a LocationInterceptionAspect, turns this field into a property, the order of the two aspects decides what the advice sees. An advice ordered before the other aspect sees the field itself. An advice ordered after it sees the property, whose value goes through the accessors and has no address, so PostSharp cannot pass it by reference and reports LA0261.
PostSharp reports the message on the target of the advice. <Field> is the name of the field in the source code.
In the following example, the [ChargeFee] aspect is ordered after [LogChanges], which intercepts the balance field, and its advice imports the field as ref.
using System;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Aspects.Dependencies;
using PostSharp.Serialization;
namespace Errors.LA0261_Error;
[PSerializable]
public class LogChangesAttribute : LocationInterceptionAspect
{
public override void OnSetValue( LocationInterceptionArgs args )
{
Console.WriteLine( $"{args.LocationName} = {args.Value}" );
args.ProceedSetValue();
}
}
[PSerializable]
[AspectTypeDependency( AspectDependencyAction.Order, AspectDependencyPosition.After, typeof(LogChangesAttribute) )]
public class ChargeFeeAttribute : MethodLevelAspect
{
[OnMethodSuccessAdvice]
public static void OnSuccess( [ImportMember( "balance" )] ref decimal balance )
{
balance -= 1m;
}
}
public class Account
{
[LogChanges]
private decimal balance;
public decimal Balance => this.balance;
// ChargeFee passes the balance field by reference, but LogChanges turns it into a property.
[ChargeFee]
public void Deposit( decimal amount ) => this.balance += amount;
}
public static class Program
{
public static void Main()
{
Account account = new Account();
account.Deposit( 100m );
Console.WriteLine( $"Balance: {account.Balance}" );
}
}
error LA0261: LA0261_Error.cs(39,17): The parameter "balance" of the advice "Errors.LA0261_Error.ChargeFeeAttribute.OnSuccess(System.Decimal@)" is bound by [ImportMember] and passed as out or ref to the field "balance", but an aspect turns this field into a property, which has no address. Pass the parameter by value or as in, or import the field as ILocationBinding<T>.
Resolution
Choose one of the following:
- Import the field as ILocationBinding<T>, and read and write it on the instance received by a ThisAttribute parameter. The values go through the property, so the other aspect sees the changes.
- Pass the parameter by value or as
in, when the advice only reads the field. The advice receives the value that the property returns. - Order the aspect before the aspect that turns the field into a property, with AspectTypeDependencyAttribute. The
refparameter then references the field itself, and the changes bypass the other aspect.
In the following example, the advice writes the balance through a location binding, so [LogChanges] also logs the fee.
using System;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Aspects.Dependencies;
using PostSharp.Serialization;
namespace Errors.LA0261_Fixed;
[PSerializable]
public class LogChangesAttribute : LocationInterceptionAspect
{
public override void OnSetValue( LocationInterceptionArgs args )
{
Console.WriteLine( $"{args.LocationName} = {args.Value}" );
args.ProceedSetValue();
}
}
[PSerializable]
[AspectTypeDependency( AspectDependencyAction.Order, AspectDependencyPosition.After, typeof(LogChangesAttribute) )]
public class ChargeFeeAttribute : MethodLevelAspect
{
// The location binding reads and writes the balance through the property, so LogChanges sees the fee.
[OnMethodSuccessAdvice]
public static void OnSuccess( [This] object account, [ImportMember( "balance" )] ILocationBinding<decimal> balance )
{
balance.SetValue( ref account, Arguments.Empty, balance.GetValue( ref account, Arguments.Empty ) - 1m );
}
}
public class Account
{
[LogChanges]
private decimal balance;
public decimal Balance => this.balance;
[ChargeFee]
public void Deposit( decimal amount ) => this.balance += amount;
}
public static class Program
{
public static void Main()
{
Account account = new Account();
account.Deposit( 100m );
Console.WriteLine( $"Balance: {account.Balance}" );
}
}
balance = 100
balance = 99
Balance: 99
See Also
Reference
ImportMemberAttribute
ILocationBinding<T>
AspectTypeDependencyAttribute
Other Resources
Advice Parameters
Coping with Several Aspects on the Same Target