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>
:- SelectReferencedAssembly gets a referenced assembly,
- SelectReflectionType gets a type given by
System.Type
.
- Performance improvements:
- The right side of query operators like
IAspectReceiver.SelectMany()
,IAspectReceiver.SelectTypes
orIAspectReceiver.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.
- The right side of query operators like
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
, orParameterOrAttribute
. 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 toNamespace
, 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:
Adding WithType and WithNullability extension methods for IType to override the inferred type or nullability of a captured expression.
Ability to evaluate a T# template into an IStatement thanks to the StatementFactory.FromTemplate method.
New concept IStatementList to represent an unresolved list of statements. Statement lists can be built from an
IStatement
orIEnumerable<IStatement>
using the new extension methods AsList and UnwrapBlock or with the new class StatementListBuilder.New class SwitchStatementBuilder to dynamically create a
switch
statement (cases can be added programmatically — only literal case labels are currently supported).New method CreateInvokeExpression generating an IExpression that represents a method invocation. Can be called outside of a template context.
New interface IConstructorInvoker with its Invoke and CreateInvokeExpression methods.
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
orMyType.Y
becomesX
orY
). - 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
ormeta.CompileTime
when it is not obvious from the context). Lambda expressions returningdynamic
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 typeT
. - 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 usingAdviserExtensions.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:
- Use one of the variants where the strictness is made explicit:
- Strict: [StrictlyPositive], [StrictlyNegative], [StrictlyLessThan] and [StrictlyGreaterThan]
- Non-strict: [NonNegative], [NonPositive], [LessThanOrEqual] and [GreaterThanOrEqual].
- Or set the DefaultInequalityStrictness contract option using the ConfigureContracts fabric extension method.
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 theIServiceProvider
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 andCommandFlags.PreferMaster
for write operations. These default values can be modified thanks to theReadCommandFlags
andWriteCommandFlags
properties of theRedisCachingBackendConfiguration
class. StackExchange.Redis
was updated to 2.8.- The Redis mehtod now consumes
IConnectionMultiplexer
from theIServiceProvider
by default.
- By default, Metalama Caching will use the
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 toC
is of kindGenericArgument
and no longerBaseType | 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 theItemRemoved
event.- The
CacheValue
class has been replaced by the existingCacheItem
class. - The
ICachingSerializer
interface has been refactored to work withBinaryReader
andBinaryWriter
.