Open sandboxFocusImprove this doc

PS0089: Field Accessed by Reference Escapes Its Aspect

Severity Warning
Reported by PostSharp
Message The field "<Field>" is loaded by reference, which prevents aspects from intercepting this access. To call an instance method of the field, use the Post.GetValue method. To change a member of the field or pass the field by reference, copy it to a local variable, change or pass the local variable, and assign the local variable back to the field [in method "<Method>"].

Cause

An aspect that intercepts a field, such as a LocationInterceptionAspect, turns the field into a property: PostSharp replaces each read and each assignment of the field with a call of the getter or the setter, which run the aspect. A reference to the field cannot be replaced in this way. The C# compiler loads the address of a field instead of its value in the following cases:

  • The code changes a member of a structure stored in the field, as in this.location.X = x, or reads a member of it, as in this.location.X.
  • The code calls an instance method of the structure stored in the field.
  • The code passes the field to a ref, out or in parameter.

PostSharp then leaves the access as it is and reports PS0089 on its line: the build succeeds, but the aspect does not see this access. PostSharp does not report the message when the code sets the field to its default value, or calls a method of a structure of the System namespace, such as DateTime. <Method> is the method that contains the access.

In the following example, the [Trace] aspect intercepts the assignments of the location field, but MoveRight changes the X field of the structure in place.

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

namespace Errors.PS0089_Error;

[PSerializable]
public class TraceAttribute : LocationInterceptionAspect
{
    public override void OnSetValue( LocationInterceptionArgs args )
    {
        Console.WriteLine( $"Setting {args.LocationName} to {args.Value}." );
        args.ProceedSetValue();
    }
}

public struct Point
{
    public Point( int x, int y )
    {
        this.X = x;
        this.Y = y;
    }

    public int X;
    public int Y;

    public override string ToString() => $"({this.X}, {this.Y})";
}

public class Shape
{
    [Trace]
    private Point location;

    public void MoveRight( int distance )
    {
        // Changes the structure in place, through a reference to the location field.
        this.location.X += distance;
    }

    public string Describe() => $"Shape at {this.location}";
}

public static class Program
{
    public static void Main()
    {
        Shape shape = new();
        shape.MoveRight( 5 );
        Console.WriteLine( shape.Describe() );
    }
}
warning PS0089: PS0089_Error.cs(39,9): The field "Errors.PS0089_Error.Shape.location" is loaded by reference, which prevents aspects from intercepting this access. To call an instance method of the field, use the Post.GetValue method. To change a member of the field or pass the field by reference, copy it to a local variable, change or pass the local variable, and assign the local variable back to the field [in method "Errors.PS0089_Error.Shape.MoveRight(System.Int32)"].

The program runs, but the aspect does not trace the move:

Shape at (5, 0)

Resolution

  • To change a member of the structure, copy the field to a local variable, change the local variable, and assign it back to the field. Better, make the structure immutable and assign a new value to the field.
  • To call an instance method of the structure, pass the field to GetValue<T>(T), as in Post.GetValue( this.location ).ToString(): the method is called on a copy of the value, which the getter returns.
  • To pass the field by reference, copy it to a local variable, pass the local variable, and assign it back to the field after the call.

In the following example, MoveRight changes a copy of the structure and assigns it to the field, which the aspect intercepts.

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

namespace Errors.PS0089_Fixed;

[PSerializable]
public class TraceAttribute : LocationInterceptionAspect
{
    public override void OnSetValue( LocationInterceptionArgs args )
    {
        Console.WriteLine( $"Setting {args.LocationName} to {args.Value}." );
        args.ProceedSetValue();
    }
}

public struct Point
{
    public Point( int x, int y )
    {
        this.X = x;
        this.Y = y;
    }

    public int X;
    public int Y;

    public override string ToString() => $"({this.X}, {this.Y})";
}

public class Shape
{
    [Trace]
    private Point location;

    public void MoveRight( int distance )
    {
        // Copies the structure to a local variable, then assigns it as a whole, which the aspect intercepts.
        Point location = this.location;
        location.X += distance;
        this.location = location;
    }

    public string Describe() => $"Shape at {this.location}";
}

public static class Program
{
    public static void Main()
    {
        Shape shape = new();
        shape.MoveRight( 5 );
        Console.WriteLine( shape.Describe() );
    }
}
Setting location to (5, 0).
Shape at (5, 0)

See Also

Reference

LocationInterceptionAspect
GetValue<T>(T)

Other Resources

Intercepting Properties and Fields