Open sandboxFocusImprove this doc

Metalama 2024.2

Metalama 2024.2 has two focal points. The first is the ability to introduce classes, which closes the biggest gap with Roslyn source generators and finally makes it possible to implement patterns like memento or enum view-model. The second priority is to finalize and document the Metalama.Patterns.Observability and Metalama.Patterns.Wpf packages.

We had to make dozens of smaller improvements to the framework to reach these objectives, and they will benefit everyone.

Here is a detailed list.

Generation of classes

It is now possible to introduce (i.e. generate) whole classes by using the AdviserExtensions.IntroduceClass method. This method returns an IAdviser<T><INamedType>, which you can then use to add members to the new type.

For details, see Introducing types.

Generation and overriding of constructors

You can now introduce a constructor into an existing or new type thanks to the AdviserExtensions.IntroduceConstructor method.

You can also override any constructor using a new overload of the Override method.

Metalama.Patterns.Observability is Generally Available

The Metalama.Patterns.Observability package is now stable and fully supported.

It contains the [Observable] aspect, which implements the INotifyPropertyChanged interface.

The [Observable] aspect is incredibly advanced and capable.

Where competing solutions stop at automatic properties, our implementation supports:

  • explicit properties with references to fields, other properties, and methods,
  • child objects, i.e., properties like string FullName => $"{this.Model.FirstName} {this.Model.LastName}",
  • references to properties of the base type,
  • use of constant static methods with immutable types.

For details, see Metalama.Patterns.Observability.

Metalama.Patterns.Wpf is Generally Available

The Metalama.Patterns.Wpf package (formerly named Metalama.Patterns.Xaml) is now stable and fully supported.

It contains two aspects: [Command] and [DependencyAttribute] to reduce the WPF boilerplate code.

For details, see Metalama.Patterns.Wpf.

New package: Metalama.Patterns.Immutability

This new package defines a concept of immutable type. Types can be marked as immutable using the [Immutable] aspect or the ConfigureImmutability fabric method. This information is used by the Metalama.Patterns.Observability package to infer the mutability of properties.

For details, see Metalama.Patterns.Immutability.

Improvements in fabrics and IAspectReceiver

  • The IAmender.Outbound property is now redundant and has been marked as [Obsolete]. The IAmender<T> interface now directly derives from IAspectReceiver<TDeclaration> instead of exposing it on the Outbound property. The use of the Outbound property is still required for IAspectBuilder<TAspectTarget>.
  • New method IAspectReceiver.Tag: adds an arbitrary tag that is carried on and available for all lambdas on the right side of the Tag method for new overloads of all (or most) IAspectReceiver methods.
  • New method IAspectReceiver.SelectTypes: gets all types in the current context (typically namespace, compilation, or current type).
  • New method IAspectReceiver.SelectTypesDerivedFrom: gets all types in the current context derived from a given type.
  • New extension methods for IAspectReceiver<ICompilation>:
  • Performance improvements:
    • The right side of query operators like IAspectReceiver.SelectMany(), IAspectReceiver.SelectTypes or IAspectReceiver.SelectTypesDerivedFrom now executes concurrently.
    • When a part of a query is used several times (typically by storing the query in a local variable), its result is cached.

Improvements in Metalama.Extensions.Architecture

  • The reference validator feature now has a concept of validator granularity (ReferenceGranularity), which accepts the values Compilation, Namespace, Type, Member, or ParameterOrAttribute. The idea is that when a validator is invariant within some level of granularity, then its predicate should only be evaluated once within the declaration at this level of granularity. For instance, if a validator granularity is set to Namespace, then all references within that namespace will be either valid or invalid at the same time.

    The validator granularity concept is essential to improve the performance of validators, as references can be validated collectively instead of one by one.

    Code written against the previous API will get obsolescence warnings. We suggest porting your validators to the new API and choosing the coarsest possible granularity.

    Additionally, the new class ReferenceEndPredicate serves as a base for predicates that depend on a single end of the reference to validate. This class is a preparation for some future feature, allowing the validation of inbound references instead of just outbound references.

  • The API has been improved with fluent And, Or and Not methods.

Improvements in diagnostic suppressions

Diagnostic suppressions can now be filtered by argument thanks to the new SuppressionDefinition.WithFilter method.

Improvements in the test framework

  • The default diff interactive tool will now be opened when an aspect test fails (i.e., the expected snapshot is different from the actual one). The feature works with DiffEngine and integrates with DiffEngineTray.

Improvements in the code model

The following changes improve your ability to generate code with Metalama:

Improvements in code formatting

  • The performance of whole-project output code formatting has been improved. Note that code formatting is disabled by default so it should not affect your standard builds, but LamaDebug builds should be faster.
  • Redundancies in member access expressions are eliminated where applicable (e.g., this.X or MyType.Y becomes X or Y).
  • Non-extension calls to extension methods in templates are transformed into extension calls. This is useful because extension methods cannot be called on dynamic types.
  • The discard parameter _, when used in templates, was renamed to __1, __2 and so on.

Improvements in advising and code templates

  • Added support for lambda statements and anonymous methods of known scope, i.e., either run-time or compile-time (the scope can be coerced using meta.RunTime or meta.CompileTime when it is not obvious from the context). Lambda expressions returning dynamic are not supported and won't be. Single-statement lambdas (e.g., () => { return 0; }) are transparently simplified into expression lambdas (e.g., () => 0).
  • New concept of Promise<T> (with its interface IPromise<T>) to represent results that are not available yet. This mechanism allows resolving chicken-or-egg issues when introducing members when a template must receive a reference to a declaration that has not been introduced yet. A Promise<T> can be passed as an argument to a template, which receives it on a parameter of type T.
  • An error will be reported when attempting to use some template-only methods from a method that is not a template.

Changes in interface implementation

  • The ImplementInterface advice no longer verifies if all interface members are present. Errors will appear during compilation. Interface members can be introduced using [InterfaceMember] as before, but also using [Introduce], or programmatically using AdviserExtensions.IntroduceMethod.
  • The IImplementInterfaceAdviceResult interface now has a ExplicitMembers property of type IAdviser<INamedType>, which allows introducing explicit (private) members.

Improvements in Metalama.Patterns.Contracts

We are finally addressing the problem where the [Positive], [Negative], [LessThan] and [GreaterThan] aspects had a non-standard behavior because they behave as if the inequality were unstrict while the standard interpretation is strict. This mistake was performed in PostSharp back in 2013 and dragged until now for backward compatibility reasons, but we eventually decided to address it.

Starting from Metalama 2024.2, using any of the [Positive], [Negative], [LessThan] or [GreaterThan] attributes will report a warning saying that the strictness of the inequality is ambiguous. You have two options to resolve the warning:

If you don't address the warning, the behavior of the ambiguous contracts will remain backward-compatible, i.e., non-standard.

We will change the default behavior and the warning in a future release.

Improvements in Metalama.Patterns.Caching

  • The Redis backend now consumes the IConnectionMultiplexer from the IServiceProvider by default, which makes it easier to use with .NET Aspire.

  • The cache key prefix now always includes the version of the serialization protocol.

  • CancellationToken parameters are automatically ignored.

  • With the Redis back-end:

    • By default, Metalama Caching will use the CommandFlags.PreferReplica for read operations and CommandFlags.PreferMaster for write operations. These default values can be modified thanks to the ReadCommandFlags and WriteCommandFlags properties of the RedisCachingBackendConfiguration class.
    • StackExchange.Redis was updated to 2.8.
    • The Redis mehtod now consumes IConnectionMultiplexer from the IServiceProvider by default.

Improvements in supportability

When troubleshooting Metalama, it is now possible to enable tracing and direct it to the standard output just using an environment variable.

For details, see Enabling logging.

Breaking Changes

  • The ReferencePredicate class has a new abstract property ReferencePredicate. Its constructor now requires a ReferencePredicateBuilder.
  • ReferenceValidationContext no longer reports several ReferenceKinds, but only the deepest one. For instance, in class A : List<C>;, the reference to C is of kind GenericArgument and no longer BaseType | GenericArgument. Combined flags added complexity, and we did not see a use case for them.
  • Projects that were using transitive reference validators (or architecture constraints), if they were built with a previous version of Metalama, must be rebuilt.
  • Relationships specified with AspectOrderAttribute are now applied to derived aspect classes by default. To revert to the previous behavior, set the ApplyToDerivedTypes property to false.
  • An error will be reported when attempting to use some compile-time methods (for instance, meta.CompileTime) from a method that is not a template. In prior versions, these methods had no effect and were only confusing.
  • Metalama.Patterns.Contracts: Some virtual methods of the RangeAttribute and ContractTemplates classes have changed; overrides must be adapted.
  • Metalama.Patterns.Caching:
    • CachingBackend.Clear() no longer raises the ItemRemoved event.
    • The CacheValue class has been replaced by the existing CacheItem class.
    • The ICachingSerializerinterface has been refactored to work with BinaryReader and BinaryWriter.