Open sandboxFocusImprove this doc

LAMA5162: The method called by a computed property isn't known to be constant

Severity Warning
Reported by Metalama.Patterns.Observability
Message The '<Member>' <Kind> cannot be observed because it is not known to be constant: it is an instance method of a type that is not immutable, or it has a parameter of a type that is not immutable. If the <Kind> always returns the same value for the same arguments, mark it with [Constant] or call ConfigureObservability via a fabric.

Cause

The [Observable] aspect analyzes the getter of each computed property to find the fields and properties it depends on. When the getter calls a method, the aspect only tracks the arguments of the call. This is correct only if the method is constant, that is, if it always returns the same value for the same arguments.

A method is considered constant when it's marked with [Constant] or configured with an observability contract, when it's a void method without out or ref parameters, or when all its inputs are immutable: it's static or belongs to an immutable type, and all its parameters are of immutable types.

Metalama reports LAMA5162 when a computed property calls a method that doesn't meet any of these conditions. The aspect still generates code, but the property isn't notified when the state read by the method changes. In the message, <Member> is the method and <Kind> is the kind of symbol, method.

In the following example, Total calls ApplyDiscount, an instance method of the mutable Order class.

Source Code
1using Metalama.Patterns.Observability;
2

3namespace Doc.LAMA5162.Error;
4
5[Observable]
6public partial class Order
7{
8    public decimal Subtotal { get; set; }
9


10    // Warning: ApplyDiscount is an instance method of a mutable type, so it isn't known to be constant.
















    Warning LAMA5162: The 'Order.ApplyDiscount(decimal)' method cannot be observed because it is not known to be constant: it is an instance method of a type that is not immutable, or it has a parameter of a type that is not immutable. If the method always returns the same value for the same arguments, mark it with [Constant] or call ConfigureObservability via a fabric.

11    public decimal Total => this.ApplyDiscount( this.Subtotal );
12
13    private decimal ApplyDiscount( decimal amount ) => amount > 100 ? amount * 0.9m : amount;







14}
15
Transformed Code
1using System.ComponentModel;
2using Metalama.Patterns.Observability;
3
4namespace Doc.LAMA5162.Error;
5
6[Observable]
7public partial class Order : INotifyPropertyChanged
8{
9    private decimal _subtotal;
10
11    public decimal Subtotal
12    {
13        get
14        {
15            return _subtotal;
16        }
17
18        set
19        {
20            if (_subtotal != value)
21            {
22                _subtotal = value;
23                OnPropertyChanged("Total");
24                OnPropertyChanged("Subtotal");
25            }
26        }
27    }
28
29    // Warning: ApplyDiscount is an instance method of a mutable type, so it isn't known to be constant.
30    public decimal Total => this.ApplyDiscount(this.Subtotal);
31
32    private decimal ApplyDiscount(decimal amount) => amount > 100 ? amount * 0.9m : amount;
33
34    protected virtual void OnPropertyChanged(string propertyName)
35    {
36        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
37    }
38
39    public event PropertyChangedEventHandler? PropertyChanged;
40}
41

Resolution

Do one of the following:

  • If the result of the method depends only on its arguments, mark the method with [Constant], as in the following example.
  • To mark many methods as constant, for example all methods of a helper class, call ConfigureObservability from a fabric and set the observability contract to ObservabilityContract.Constant.
  • If the method doesn't use instance state, make it static. A static method whose parameters are all of immutable types is considered constant automatically.
  • If the method reads mutable state, pass this state as arguments, or move the logic into the getter, so that the aspect can track it.

Don't mark a method as constant when its result depends on mutable state: the property wouldn't be notified when this state changes.

Source Code
1using Metalama.Patterns.Observability;
2

3namespace Doc.LAMA5162.Fixed;
4
5[Observable]
6public partial class Order
7{
8    public decimal Subtotal { get; set; }
9


10    public decimal Total => this.ApplyDiscount( this.Subtotal );
















11
12    // Fixed: the result depends only on the argument, so the method is marked as constant.
13    [Constant]
14    private decimal ApplyDiscount( decimal amount ) => amount > 100 ? amount * 0.9m : amount;
15}
16
Transformed Code
1using System.ComponentModel;
2using Metalama.Patterns.Observability;
3
4namespace Doc.LAMA5162.Fixed;
5
6[Observable]
7public partial class Order : INotifyPropertyChanged
8{
9    private decimal _subtotal;
10
11    public decimal Subtotal
12    {
13        get
14        {
15            return _subtotal;
16        }
17
18        set
19        {
20            if (_subtotal != value)
21            {
22                _subtotal = value;
23                OnPropertyChanged("Total");
24                OnPropertyChanged("Subtotal");
25            }
26        }
27    }
28
29    public decimal Total => this.ApplyDiscount(this.Subtotal);
30
31    // Fixed: the result depends only on the argument, so the method is marked as constant.
32    [Constant]
33    private decimal ApplyDiscount(decimal amount) => amount > 100 ? amount * 0.9m : amount;
34
35    protected virtual void OnPropertyChanged(string propertyName)
36    {
37        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
38    }
39
40    public event PropertyChangedEventHandler? PropertyChanged;
41}
42

See also