| Severity | Warning |
| Reported by | Metalama.Patterns.Observability |
| Message | Local variables of type '<Type>' cannot be observed: only local variables of immutable types, such as primitive types, are supported. |
Cause
The [Observable] aspect analyzes the getter of each computed property to find the fields and properties it depends on, including properties of child objects such as this.Address.City. It follows these member accesses only when they start from a member of the current object. It doesn't follow an object stored in a local variable.
Metalama reports LAMA5165 when the getter declares a local variable whose type isn't immutable. Because the variable can reference a mutable object, changes to this object's properties aren't tracked, and the computed property isn't notified. Variables of immutable types, such as int, string, or types marked as immutable, are supported. In the message, <Type> is the type of the variable.
In the following example, the getter of DeliveryCity stores an Address in a local variable and reads its City property.
1using Metalama.Patterns.Observability;
2
3namespace Doc.LAMA5165.Error;
4
5[Observable]
6public partial class Customer
7{
8 public bool ShipToBillingAddress { get; set; } = true;
9
10 public Address BillingAddress { get; set; } = new();
11
12 public Address ShippingAddress { get; set; } = new();
13
14 public string DeliveryCity
15 {
16 get
17 {
18 // Warning: Address is a mutable type, so the local variable can't be observed.
Warning LAMA5165: Local variables of type 'Address' cannot be observed: only local variables of immutable types, such as primitive types, are supported.
19 Address address = this.ShipToBillingAddress ? this.BillingAddress : this.ShippingAddress;
20
21 return address.City;
22 }
23 }
24}
25
1using System.ComponentModel;
2using Metalama.Patterns.Observability;
3
4namespace Doc.LAMA5165.Error;
5
6[Observable]
7public partial class Customer : INotifyPropertyChanged
8{
9 private bool _shipToBillingAddress = true;
10
11 public bool ShipToBillingAddress
12 {
13 get
14 {
15 return _shipToBillingAddress;
16 }
17
18 set
19 {
20 if (_shipToBillingAddress != value)
21 {
22 _shipToBillingAddress = value;
23 OnPropertyChanged("DeliveryCity");
24 OnPropertyChanged("ShipToBillingAddress");
25 }
26 }
27 }
28
29 private Address _billingAddress = new();
30
31 public Address BillingAddress
32 {
33 get
34 {
35 return _billingAddress;
36 }
37
38 set
39 {
40 if (!object.ReferenceEquals(value, _billingAddress))
41 {
42 var oldValue = _billingAddress;
43 if (oldValue != null)
44 {
45 oldValue.PropertyChanged -= _handleBillingAddressPropertyChanged;
46 }
47
48 _billingAddress = value;
49 OnObservablePropertyChanged("BillingAddress", oldValue, (INotifyPropertyChanged?)value);
50 OnPropertyChanged("DeliveryCity");
51 OnPropertyChanged("BillingAddress");
52 SubscribeToBillingAddress(value);
53 }
54 }
55 }
56
57 private Address _shippingAddress = new();
58
59 public Address ShippingAddress
60 {
61 get
62 {
63 return _shippingAddress;
64 }
65
66 set
67 {
68 if (!object.ReferenceEquals(value, _shippingAddress))
69 {
70 var oldValue = _shippingAddress;
71 if (oldValue != null)
72 {
73 oldValue.PropertyChanged -= _handleShippingAddressPropertyChanged;
74 }
75
76 _shippingAddress = value;
77 OnObservablePropertyChanged("ShippingAddress", oldValue, (INotifyPropertyChanged?)value);
78 OnPropertyChanged("DeliveryCity");
79 OnPropertyChanged("ShippingAddress");
80 SubscribeToShippingAddress(value);
81 }
82 }
83 }
84 public string DeliveryCity
85 {
86 get
87 {
88 // Warning: Address is a mutable type, so the local variable can't be observed.
89 Address address = this.ShipToBillingAddress ? this.BillingAddress : this.ShippingAddress;
90
91 return address.City;
92 }
93 }
94
95 private PropertyChangedEventHandler? _handleBillingAddressPropertyChanged;
96 private PropertyChangedEventHandler? _handleShippingAddressPropertyChanged;
97
98 public Customer()
99 {
100 SubscribeToBillingAddress(BillingAddress);
101 SubscribeToShippingAddress(ShippingAddress);
102 }
103
104 [ObservedExpressions("BillingAddress", "ShippingAddress")]
105 protected virtual void OnChildPropertyChanged(string parentPropertyPath, string propertyName)
106 {
107 }
108
109 [ObservedExpressions("BillingAddress", "ShippingAddress")]
110 protected virtual void OnObservablePropertyChanged(string propertyPath, INotifyPropertyChanged? oldValue, INotifyPropertyChanged? newValue)
111 {
112 }
113
114 protected virtual void OnPropertyChanged(string propertyName)
115 {
116 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
117 }
118
119 private void SubscribeToBillingAddress(Address value)
120 {
121 if (value != null)
122 {
123 _handleBillingAddressPropertyChanged ??= HandlePropertyChanged;
124 value.PropertyChanged += _handleBillingAddressPropertyChanged;
125 }
126
127 void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
128 {
129 {
130 var propertyName = e.PropertyName;
131 switch (propertyName)
132 {
133 default:
134 OnChildPropertyChanged("BillingAddress", propertyName);
135 break;
136 }
137 }
138 }
139 }
140
141 private void SubscribeToShippingAddress(Address value)
142 {
143 if (value != null)
144 {
145 _handleShippingAddressPropertyChanged ??= HandlePropertyChanged;
146 value.PropertyChanged += _handleShippingAddressPropertyChanged;
147 }
148
149 void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
150 {
151 {
152 var propertyName = e.PropertyName;
153 switch (propertyName)
154 {
155 default:
156 OnChildPropertyChanged("ShippingAddress", propertyName);
157 break;
158 }
159 }
160 }
161 }
162
163 public event PropertyChangedEventHandler? PropertyChanged;
164}
165
1using Metalama.Patterns.Observability;
2
3namespace Doc.LAMA5165.Error;
4
5[Observable]
6public partial class Address
7{
8 public string City { get; set; } = "";
9}
10
Resolution
Do one of the following:
- Remove the local variable and access the members through the properties of the current object, as in the following example.
- If the type of the variable is immutable but Metalama doesn't know it, mark it as immutable. For details, see Metalama.Patterns.Immutability.
1using Metalama.Patterns.Observability;
2
3namespace Doc.LAMA5165.Fixed;
4
5[Observable]
6public partial class Customer
7{
8 public bool ShipToBillingAddress { get; set; } = true;
9
10 public Address BillingAddress { get; set; } = new();
11
12 public Address ShippingAddress { get; set; } = new();
13
14 public string DeliveryCity
15 {
16 get
17 {
18 // Fixed: City is accessed through the properties, without a local variable.
19 return this.ShipToBillingAddress ? this.BillingAddress.City : this.ShippingAddress.City;
20 }
21 }
22}
23
1using System.ComponentModel;
2using Metalama.Patterns.Observability;
3
4namespace Doc.LAMA5165.Fixed;
5
6[Observable]
7public partial class Customer : INotifyPropertyChanged
8{
9 private bool _shipToBillingAddress = true;
10
11 public bool ShipToBillingAddress
12 {
13 get
14 {
15 return _shipToBillingAddress;
16 }
17
18 set
19 {
20 if (_shipToBillingAddress != value)
21 {
22 _shipToBillingAddress = value;
23 OnPropertyChanged("DeliveryCity");
24 OnPropertyChanged("ShipToBillingAddress");
25 }
26 }
27 }
28
29 private Address _billingAddress = new();
30
31 public Address BillingAddress
32 {
33 get
34 {
35 return _billingAddress;
36 }
37
38 set
39 {
40 if (!object.ReferenceEquals(value, _billingAddress))
41 {
42 var oldValue = _billingAddress;
43 if (oldValue != null)
44 {
45 oldValue.PropertyChanged -= _handleBillingAddressPropertyChanged;
46 }
47
48 _billingAddress = value;
49 OnObservablePropertyChanged("BillingAddress", oldValue, (INotifyPropertyChanged?)value);
50 OnPropertyChanged("DeliveryCity");
51 OnPropertyChanged("BillingAddress");
52 SubscribeToBillingAddress(value);
53 }
54 }
55 }
56
57 private Address _shippingAddress = new();
58
59 public Address ShippingAddress
60 {
61 get
62 {
63 return _shippingAddress;
64 }
65
66 set
67 {
68 if (!object.ReferenceEquals(value, _shippingAddress))
69 {
70 var oldValue = _shippingAddress;
71 if (oldValue != null)
72 {
73 oldValue.PropertyChanged -= _handleShippingAddressPropertyChanged;
74 }
75
76 _shippingAddress = value;
77 OnObservablePropertyChanged("ShippingAddress", oldValue, (INotifyPropertyChanged?)value);
78 OnPropertyChanged("DeliveryCity");
79 OnPropertyChanged("ShippingAddress");
80 SubscribeToShippingAddress(value);
81 }
82 }
83 }
84 public string DeliveryCity
85 {
86 get
87 {
88 // Fixed: City is accessed through the properties, without a local variable.
89 return this.ShipToBillingAddress ? this.BillingAddress.City : this.ShippingAddress.City;
90 }
91 }
92
93 private PropertyChangedEventHandler? _handleBillingAddressPropertyChanged;
94 private PropertyChangedEventHandler? _handleShippingAddressPropertyChanged;
95
96 public Customer()
97 {
98 SubscribeToBillingAddress(BillingAddress);
99 SubscribeToShippingAddress(ShippingAddress);
100 }
101
102 [ObservedExpressions("BillingAddress", "ShippingAddress")]
103 protected virtual void OnChildPropertyChanged(string parentPropertyPath, string propertyName)
104 {
105 }
106
107 [ObservedExpressions("BillingAddress", "ShippingAddress")]
108 protected virtual void OnObservablePropertyChanged(string propertyPath, INotifyPropertyChanged? oldValue, INotifyPropertyChanged? newValue)
109 {
110 }
111
112 protected virtual void OnPropertyChanged(string propertyName)
113 {
114 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
115 }
116
117 private void SubscribeToBillingAddress(Address value)
118 {
119 if (value != null)
120 {
121 _handleBillingAddressPropertyChanged ??= HandlePropertyChanged;
122 value.PropertyChanged += _handleBillingAddressPropertyChanged;
123 }
124
125 void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
126 {
127 {
128 var propertyName = e.PropertyName;
129 switch (propertyName)
130 {
131 case "City":
132 OnPropertyChanged("DeliveryCity");
133 OnChildPropertyChanged("BillingAddress", "City");
134 break;
135 default:
136 OnChildPropertyChanged("BillingAddress", propertyName);
137 break;
138 }
139 }
140 }
141 }
142
143 private void SubscribeToShippingAddress(Address value)
144 {
145 if (value != null)
146 {
147 _handleShippingAddressPropertyChanged ??= HandlePropertyChanged;
148 value.PropertyChanged += _handleShippingAddressPropertyChanged;
149 }
150
151 void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
152 {
153 {
154 var propertyName = e.PropertyName;
155 switch (propertyName)
156 {
157 case "City":
158 OnPropertyChanged("DeliveryCity");
159 OnChildPropertyChanged("ShippingAddress", "City");
160 break;
161 default:
162 OnChildPropertyChanged("ShippingAddress", propertyName);
163 break;
164 }
165 }
166 }
167 }
168
169 public event PropertyChangedEventHandler? PropertyChanged;
170}
171
1using Metalama.Patterns.Observability;
2
3namespace Doc.LAMA5165.Fixed;
4
5[Observable]
6public partial class Address
7{
8 public string City { get; set; } = "";
9}
10