| Severity | Warning |
| Reported by | Metalama.Framework |
| Message | '<Type>' must be written in an immutable style because it is an aspect, a fabric, a validator, or another type bound by the [ImmutableType] contract, but '<Member>' is assigned here, outside a constructor. Move the assignment into a constructor or an 'init' accessor. |
Cause
Aspects, fabrics and validators must be written in an immutable style, because Metalama can share one instance among several threads and several compilations. A type is subject to this rule when it carries the [ImmutableType] attribute or when it derives from, or implements, a type that requires immutability, such as IAspect or Fabric.
A field or an automatic property whose write access is private is accepted by the declaration rules, which cannot see every assignment otherwise. The analyzer instead checks the assignments themselves, and reports LAMA0887 on each one that happens outside a constructor or an init accessor of the declaring type. In the message, <Type> is the type that must be immutable and <Member> is the name of the field or property that is written.
The rule applies to the following write operations on an instance member:
- simple, compound and null-coalescing assignments, such as
this._count = 1,this._count += 1orthis._cache ??= value; - increments and decrements, such as
this._count++; - deconstructions, where each target member is reported separately;
- assignments in a lambda expression or a local function, even when it is written inside a constructor, because the delegate can run at any later time;
- assignments to a member of another instance of the same type, even in a constructor.
The analyzer doesn't report object initializers and with expressions, static and constant members, members that are advice (for example members marked with the [Template] or [Introduce] attributes, which are injected into the target code and aren't state of the aspect), or ref arguments. An out argument is reported by LAMA0888.
This diagnostic is reported by a Roslyn analyzer, and snapshot tests of aspects don't run analyzers, so this article has no example.
Resolution
- Compute the value in the constructor and assign it there, or initialize the field with an initializer.
- Use an
initaccessor for a property that callers set when they create the object. - If the value changes over time, don't keep it in the aspect. Store it in an object that is created for each use, or in a local variable.