Open sandboxFocusImprove this doc

DOM028: Method Assigns the Fields of a Structure Field One by One

Severity Warning
Reported by PostSharp.Patterns.Model
Message The method "<Method>" uses an indirect access to field "<Field>" of type "<FieldType>" which is a value type. Such changes may not be observable. Please consider assigning the structure as a whole instead of its individual fields.

Cause

The NotifyPropertyChangedAttribute aspect detects the changes of a field when the field is assigned. When a field holds a structure, a statement such as this.location.X = x changes the structure in place, through a reference to the field, without assigning the field: the aspect does not see the change and does not notify the properties that depend on the field.

PostSharp reports DOM028 on each method of the class that assigns a field of a structure held in a field of the class, in the form this.field.member = value. <FieldType> is the name of the structure type. The message is a warning: the build succeeds, but the change raises no notification.

The same code also makes PostSharp report the warning PS0089, because the aspect cannot intercept an access to the field by reference.

In the following example, the MoveTo method assigns the X and Y fields of the location structure separately.

using System;
using System.ComponentModel;
using PostSharp.Patterns.Model;

namespace Errors.DOM028_Error;

public struct Point
{
    public int X;
    public int Y;

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

[NotifyPropertyChanged]
public class Shape
{
    private Point location;

    public Point Location => this.location;

    // The method assigns the fields of the structure one by one.
    public void MoveTo( int x, int y )
    {
        this.location.X = x;
        this.location.Y = y;
    }
}

public static class Program
{
    public static void Main()
    {
        var shape = new Shape();
        ((INotifyPropertyChanged) shape).PropertyChanged +=
            ( _, e ) => Console.WriteLine( $"PropertyChanged: {e.PropertyName}" );

        shape.MoveTo( 3, 4 );
        Console.WriteLine( $"Location: {shape.Location}" );
    }
}
warning DOM028: DOM028_Error.cs(23,17): The method "Errors.DOM028_Error.Shape.MoveTo(System.Int32, System.Int32)" uses an indirect access to field "Errors.DOM028_Error.Shape.location" of type "Point" which is a value type. Such changes may not be observable. Please consider assigning the structure as a whole instead of its individual fields.
warning PS0089: The field "Errors.DOM028_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.DOM028_Error.Shape.MoveTo(System.Int32, System.Int32)"].

The program runs, but moving the shape raises no notification for Location:

Location: (3, 4)

Resolution

Assign the structure as a whole: build a new value and assign it to the field, for example with an object initializer or a with expression.

In the following example, MoveTo assigns a new Point to location, and the aspect notifies Location.

using System;
using System.ComponentModel;
using PostSharp.Patterns.Model;

namespace Errors.DOM028_Fixed;

public struct Point
{
    public int X;
    public int Y;

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

[NotifyPropertyChanged]
public class Shape
{
    private Point location;

    public Point Location => this.location;

    public void MoveTo( int x, int y )
    {
        // The method assigns the structure as a whole.
        this.location = new Point { X = x, Y = y };
    }
}

public static class Program
{
    public static void Main()
    {
        var shape = new Shape();
        ((INotifyPropertyChanged) shape).PropertyChanged +=
            ( _, e ) => Console.WriteLine( $"PropertyChanged: {e.PropertyName}" );

        shape.MoveTo( 3, 4 );
        Console.WriteLine( $"Location: {shape.Location}" );
    }
}
PropertyChanged: Location
Location: (3, 4)

See Also

Reference

NotifyPropertyChangedAttribute

Other Resources

Implementing INotifyPropertyChanged
Handling Corner Cases of the NotifyPropertyChanged Aspect