Open sandboxFocusImprove this doc

LAMA0882: A member of an immutable type has a mutable type

Severity Warning
Reported by Metalama.Framework (Roslyn analyzer)
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 '<MemberType>' is not immutable. Path: <Path>.

Cause

An aspect is instantiated once and then reused for every declaration to which it applies, and at design time across compilations. State kept in an aspect therefore leaks from one target to the next. For this reason, IAspect and Fabric are marked with the [ImmutableType] attribute, and every type that derives from or implements a marked type, such as every aspect, fabric and validator, must be written in an immutable style. You can also mark your own types with [ImmutableType].

An analyzer in the Metalama.Framework package verifies this contract. It reports LAMA0882 when the field, or the automatically implemented property, of such a type is of a type that isn't immutable. In the message, <Type> is the type that must be immutable, <MemberType> is the offending type, and <Path> is the chain of declarations that leads from <Type> to the reason why <MemberType> isn't immutable. For example, a path can look like OrderLogger -> _lines -> List<string> (the type is not marked [ImmutableType]).

The analyzer decides whether a type is immutable as follows:

  • Primitive types, string, enumerations, delegates, and many well-known value types and immutable collections are immutable. A tuple, a nullable value type, or an immutable collection such as ImmutableArray<T> is immutable only if its elements or type arguments are.
  • A class or struct is immutable only if it is marked with [ImmutableType]. The analyzer then verifies the members of this type where it is declared. A type that isn't marked is considered mutable, even if all its fields are read-only.
  • An array is always mutable, because its elements can be replaced, and so are object, dynamic, and the mutable .NET collections such as List<T> and Dictionary<TKey, TValue>.
  • A type parameter is immutable inside the generic definition. The obligation moves to the code that supplies the type argument, and a constructed generic type such as Box<StringBuilder> is reported if the type stores that argument.

The analyzer reports each member once. If the member is also writable from outside the type, LAMA0880 or LAMA0881 is reported instead, and you must fix it before this one. Members that are templates or introduced by the aspect, such as members marked [Template] or [Introduce], are exempt because they describe code generated in the target. When the member type is an interface or an abstract class that isn't marked, the analyzer reports LAMA0884 instead.

This diagnostic is reported by a Roslyn analyzer, which doesn't run in the snapshot tests of this documentation, so it can't be shown in an example.

Resolution

Choose the option that suits the member:

  • Replace the mutable type with an immutable one: ImmutableArray<T> instead of an array or List<T>, ImmutableDictionary<TKey, TValue> instead of Dictionary<TKey, TValue>, and so on. Make sure that the elements are immutable too.
  • If the type is yours, mark it with [ImmutableType] and make all its instance fields read-only and immutable. The analyzer verifies it.
  • If the type isn't yours and you know that it's immutable, declare it to the analyzer with a MetalamaImmutableType item in the project file, using the full metadata name of the type, with its arity for a generic type (see LAMA0885).
  • If the member holds state that depends on the target of the aspect, don't store it in the aspect at all. Compute it in BuildAspect or in the template instead.
  • If the warning is unwanted on one declaration, suppress it with #pragma warning disable LAMA0882. If your project implements the framework itself, set the MetalamaEnforceImmutabilityContract MSBuild property to false.

See also