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:
- Add a reference to the
Metalama.Patterns.Observabilitypackage. - 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.
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
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.