| Severity | Warning |
| Reported by | Metalama.Patterns.Observability |
| Message | The members of '<Type>' cannot be observed because '<Type>' does not implement INotifyPropertyChanged and is not immutable. Implement INotifyPropertyChanged in '<Type>', for example with the [Observable] aspect, or mark '<Type>' as immutable. |
Cause
The [Observable] aspect analyzes the getters of computed properties to find their dependencies. When a getter reads a member of a child object, for example this.Person.Name, the aspect subscribes to the PropertyChanged event of the child object, here Person, to know when Name changes.
Metalama reports LAMA5161 when the type of the child object, <Type> in the message, doesn't implement INotifyPropertyChanged and isn't immutable. The aspect then can't know when the member of the child object changes, so the computed property doesn't raise a notification in this case. It still raises one when the child object itself is replaced. The warning is reported on the reference to the child object in the getter.
In the following example, DisplayName depends on Person.Name, but Person doesn't implement INotifyPropertyChanged.
1using Metalama.Patterns.Observability;
2
3namespace Doc.LAMA5161.Error;
4
5// Person does not implement INotifyPropertyChanged.
6public class Person
7{
8 public string? Name { get; set; }
9}
10
11[Observable]
12public sealed partial class PersonViewModel
13{
14 public PersonViewModel( Person person )
15 {
16 this.Person = person;
17 }
18
19 public Person Person { get; set; }
20
21 // Warning: changes of Person.Name cannot be observed.
Warning LAMA5161: The members of 'Person' cannot be observed because 'Person' does not implement INotifyPropertyChanged and is not immutable. Implement INotifyPropertyChanged in 'Person', for example with the [Observable] aspect, or mark 'Person' as immutable.
22 public string? DisplayName => this.Person.Name;
23}
24
1using System.ComponentModel;
2using Metalama.Patterns.Observability;
3
4namespace Doc.LAMA5161.Error;
5
6// Person does not implement INotifyPropertyChanged.
7public class Person
8{
9 public string? Name { get; set; }
10}
11
12[Observable]
13public sealed partial class PersonViewModel : INotifyPropertyChanged
14{
15 public PersonViewModel(Person person)
16 {
17 this.Person = person;
18 }
19
20 private Person _person = default!;
21
22 public Person Person
23 {
24 get
25 {
26 return _person;
27 }
28
29 set
30 {
31 if (!object.ReferenceEquals(value, _person))
32 {
33 _person = value;
34 OnPropertyChanged("Person");
35 }
36 }
37 }
38
39 // Warning: changes of Person.Name cannot be observed.
40 public string? DisplayName => this.Person.Name;
41
42 private void OnPropertyChanged(string propertyName)
43 {
44 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
45 }
46
47 public event PropertyChangedEventHandler? PropertyChanged;
48}
49
Resolution
Do one of the following:
- Make the type of the child object implement INotifyPropertyChanged, for example by applying the [Observable] aspect to it, as in the following example.
- If the child object can't change after construction, mark its type as immutable, for example with the [Immutable] aspect. The aspect then knows that it doesn't need to observe its members.
- If you accept that the computed property doesn't track changes of the child object, suppress the warning with the [SuppressObservabilityWarnings] attribute.
1using Metalama.Patterns.Observability;
2
3namespace Doc.LAMA5161.Fixed;
4
5// Fixed: Person implements INotifyPropertyChanged thanks to the [Observable] aspect.
6[Observable]
7public partial class Person
8{
9 public string? Name { get; set; }
10}
11
12[Observable]
13public sealed partial class PersonViewModel
14{
15 public PersonViewModel( Person person )
16 {
17 this.Person = person;
18 }
19
20 public Person Person { get; set; }
21
22 public string? DisplayName => this.Person.Name;
23}
24
1using System.ComponentModel;
2using Metalama.Patterns.Observability;
3
4namespace Doc.LAMA5161.Fixed;
5
6// Fixed: Person implements INotifyPropertyChanged thanks to the [Observable] aspect.
7[Observable]
8public partial class Person : INotifyPropertyChanged
9{
10 private string? _name;
11
12 public string? Name
13 {
14 get
15 {
16 return _name;
17 }
18
19 set
20 {
21 if (!object.ReferenceEquals(value, _name))
22 {
23 _name = value;
24 OnPropertyChanged("Name");
25 }
26 }
27 }
28
29 protected virtual void OnPropertyChanged(string propertyName)
30 {
31 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
32 }
33
34 public event PropertyChangedEventHandler? PropertyChanged;
35}
36
37[Observable]
38public sealed partial class PersonViewModel : INotifyPropertyChanged
39{
40 public PersonViewModel(Person person)
41 {
42 this.Person = person;
43 }
44
45 private Person _person = default!;
46
47 public Person Person
48 {
49 get
50 {
51 return _person;
52 }
53
54 set
55 {
56 if (!object.ReferenceEquals(value, _person))
57 {
58 var oldValue = _person;
59 if (oldValue != null)
60 {
61 oldValue.PropertyChanged -= _handlePersonPropertyChanged;
62 }
63
64 _person = value;
65 OnPropertyChanged("DisplayName");
66 OnPropertyChanged("Person");
67 SubscribeToPerson(value);
68 }
69 }
70 }
71
72 public string? DisplayName => this.Person.Name;
73
74 private PropertyChangedEventHandler? _handlePersonPropertyChanged;
75
76 private void OnPropertyChanged(string propertyName)
77 {
78 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
79 }
80
81 private void SubscribeToPerson(Person value)
82 {
83 if (value != null)
84 {
85 _handlePersonPropertyChanged ??= HandlePropertyChanged;
86 value.PropertyChanged += _handlePersonPropertyChanged;
87 }
88
89 void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
90 {
91 {
92 var propertyName = e.PropertyName;
93 switch (propertyName)
94 {
95 case "Name":
96 OnPropertyChanged("DisplayName");
97 break;
98 }
99 }
100 }
101 }
102
103 public event PropertyChangedEventHandler? PropertyChanged;
104}
105