Open sandboxFocusImprove this doc

Example: using the ready-made Observable aspect

Metalama.Patterns.Observability is an open-source package that implements INotifyPropertyChanged for you. This example applies its [Observable] aspect to a type that has a computed property. This is the approach to use in production code. The other examples of this section build a comparable aspect from scratch for didactic purposes.

Two steps are required to use the aspect:

  1. Add a reference to the Metalama.Patterns.Observability package.
  2. Add the [Observable] attribute to the type. The aspect is inherited, so a derived class does not need the attribute when a base class already has it.

Here is the transformation. The code generated by Metalama is displayed in green.

Source Code
1using Metalama.Patterns.Observability;
2
3[Observable]
4internal partial class MovingVertex
5{
6    public double X { get; set; }
7
8    public double Y { get; set; }
9
10    public double DX { get; set; }
11
12    public double DY { get; set; }
13
14    public double Velocity => Math.Sqrt( (this.DX * this.DX) + (this.DY * this.DY) );
15
16    public void ApplyTime( double time )
17    {
18        this.X += this.DX * time;
19        this.Y += this.DY * time;
20    }
21}
22
Transformed Code
1using System.ComponentModel;
2using Metalama.Patterns.Observability;
3
4[Observable]
5internal partial class MovingVertex
6: INotifyPropertyChanged
7{
8private double _x;
9
10    public double X { get { return this._x; } set { if (this._x != value) { this._x = value; this.OnPropertyChanged("X"); } } }
11    private double _y;
12
13    public double Y { get { return this._y; } set { if (this._y != value) { this._y = value; this.OnPropertyChanged("Y"); } } }
14    private double _dX;
15
16    public double DX { get { return this._dX; } set { if (this._dX != value) { this._dX = value; this.OnPropertyChanged("Velocity"); this.OnPropertyChanged("DX"); } } }
17    private double _dY;
18
19    public double DY { get { return this._dY; } set { if (this._dY != value) { this._dY = value; this.OnPropertyChanged("Velocity"); this.OnPropertyChanged("DY"); } } }
20
21    public double Velocity => Math.Sqrt( (this.DX * this.DX) + (this.DY * this.DY) );
22
23    public void ApplyTime( double time )
24    {
25        this.X += this.DX * time;
26        this.Y += this.DY * time;
27    }
28protected virtual void OnPropertyChanged(string propertyName)
29    {
30        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
31    }
32    public event PropertyChangedEventHandler? PropertyChanged;
33}
34

What the aspect generates

The Velocity property is computed from DX and DY. The aspect discovers this dependency by analyzing the type, so the setters of DX and DY raise a notification for Velocity in addition to the notification for the property being set. This is the case that the custom aspect of Building a custom INotifyPropertyChanged aspect: basic implementation cannot handle.

The generated setters also compare the new value with the current one, and raise no notification when the value is unchanged. Finally, the aspect converts the automatic properties into field-backed properties, adds the INotifyPropertyChanged interface, and adds a virtual OnPropertyChanged method that raises the event.

Running the program gives the following output. The third line shows the notification for the computed property, raised by the setter of DX.

X has changed
Y has changed
Velocity has changed
DX has changed

Going further

The aspect supports more constructs than this example shows, in particular properties that depend on the properties of child objects, and methods that are known to return the same value for the same arguments. When the aspect encounters a dependency it cannot analyze, it reports a warning in the LAMA51xx range instead of generating incorrect code. The [NotObservable], [Constant] and [SuppressObservabilityWarnings] attributes, and the ConfigureObservability fabric method, let you address these warnings. For the complete reference, see Metalama.Patterns.Observability.