Open sandboxFocusImprove this doc

LAMA5165: A computed property uses a local variable of a mutable type

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.

Source Code
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
Transformed Code
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.

Source Code
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
Transformed Code
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

See also