We've focused on two areas: first, ensuring Metalama is compatible with the latest .NET stack, and second, completing gaps left in the previous version, particularly in support for type introductions. We've also implemented minor improvements requested by the community.
We published the following preview builds: 2025.0.1-preview, 2025.0.2-preview, 2025.0.3-preview, and 2025.0.4-preview.
Support for .NET 9.0 and C# 13
C# 13 features
We tested and fixed Metalama 2025.0 for all features of C# 13:
params
collectionsref
/unsafe
in iterators and async methodsref struct
can implement interfaces- Classes can have
ref struct
constraints - New escape character in strings
- Locking on the Lock class
- Implicit indexer access in object initializers
- Overload resolution priority
partial
properties
Platform deprecation
- The minimal supported Visual Studio version is now 2022 17.6 LTSC.
- The minimal supported Roslyn version is now 4.4.0.
Third-party package dependencies have been updated.
Consistent support for source generators and interceptors
We now consistently execute source generators after any Metalama transformation. Previously, code generators were executed before Metalama at build time, causing inconsistencies with the design-time experience, as Metalama would not "see" the output of source generators.
The benefit for you is that aspects can introduce code that relies on the GeneratedRegex attribute to use build-time-generated regular expressions.
The second benefit is that you can now use Roslyn interceptors side-by-side with Metalama since they no longer conflict with our code transformations.
Improved work with introduced types
You can now use introduced types in any type construction. For instance, if Foo
is your introduced type, you can create a field or parameter of type Foo<int>
, Foo[]
, List<Foo>
, or Foo*
. This required a major refactoring of our code model.
You can also implement generic interfaces bound to a type parameter of the target type. For instance, you can now build an Equatable
aspect that generates code for the IEquatable<T>
interface, even for introduced types.
T# improvements
Dynamic definition of local variables
It's now possible to dynamically define local variables with the new DefineLocalVariable method, which offers the following overloads:
// Explicitly typed
meta.DefineLocalVariable( string nameHint, IType type ) : IExpression
meta.DefineLocalVariable( string nameHint, IType type, dynamic? initializerExpression ) : IExpression
meta.DefineLocalVariable( string nameHint, IType type, IExpresson? initializerExpression ) : IExpression
meta.DefineLocalVariable( string nameHint, Type type ) : IExpression
meta.DefineLocalVariable( string nameHint, Type type, dynamic? initializerExpression ) : IExpression
meta.DefineLocalVariable( string nameHint, Type type, IExpression? initializerExpression ) : IExpression
// var typed
meta.DefineLocalVariable( string nameHint, dynamic? initializerExpression ) : IExpression
meta.DefineLocalVariable( string nameHint, IExpresson? initializerExpression ) : IExpression
The nameHint
parameter suggests the desired local variable name, but the actual name will be chosen dynamically by appending a numerical suffix in case of lexical conflicts with other symbols in the scope.
For details, see the updated Generating run-time code article.
Introduction of static virtual, abstract, and partial members
You can now introduce static virtual
, abstract
, and partial
members thanks to the usual IntroduceMethod, IntroduceProperty and IntroduceEvent methods.
The partial
keyword can be set using the IMemberBuilder.IsPartial
property.
When introducing a partial
or abstract
member, the template's body is ignored. If you don't want to supply a body altogether, you can mark the template member as extern
, which will make the C# compiler happy about your template being unimplemented.
Introduction of interfaces
You can now introduce an interface in the same way as you can introduce classes, by using the IntroduceInterface method.
Introduction of extension methods
You can now introduce an extension method by marking the method as static
and the first parameter as this
, using either the [This]
attribute or the IParameterBuilder.IsThis
property.
Suppression of well-known irrelevant warnings in aspects
In previous versions, the C# compiler and some analyzers could report irrelevant warnings in aspect code, especially in T# templates. For instance, they could complain that a field is uninitialized or suggest making a method static because they would not see the context in which these template declarations are used.
Metalama 2025.0 now automatically suppresses these warnings, which means that you no longer need to use #pragma warning disable
in your aspects—or at least less often.
Async and background WPF commands
The [Command]
aspect now supports asynchronous commands. The following signatures are now supported for the Execute
method, with or without a data parameter, with or without a CancellationToken
.
[Command]
Task ExecuteAsync();
[Command]
Task ExecuteAsync( T );
[Command]
Task ExecuteAsync( CancellationToken );
[Command]
Task ExecuteAsync( T, CancellationToken );
Asynchronous commands are represented by the AsyncDelegateCommand class, which is similar to CommunityToolkit.Mvvm.Input.AsyncRelayCommand
. It allows you to easily cancel or track the completion of the task.
You can now force the Execute
method to run in a background thread (instead of the UI thread) by setting the Background property. This works for both void
and Task
methods:
[Command( Background = true )]
void Execute();
[Command( Background = true )]
Task ExecuteAsync();
Background commands are also represented by an AsyncDelegateCommand, even non-Task
ones.
Other small improvements
- Test framework: Added test options
@Repeat(<int>)
and@RandomSeed(<int>)
to help reproduce random issues. - Code model:
ToDisplayString
andToString
implemented for introduced declarations. - Representation of overridden fields has been made more consistent.
- Some type predicate methods renamed. The old methods have been marked as obsolete.
- IType.Is -> IsConvertibeTo
- EligibilityBuilder.MustBe -> MustBeConvertibleTo or MustEqual
- EligibilityBuilder.MustBeOfType -> MustBeInstanceOfType
Breaking changes
- The
ReferenceResolutionOptions
enum and all parameters ofReferenceResolutionOptions
inIRef.GetTarget
have been removed. - Casting a non-dynamic expression to IExpression no longer works. A call of Capture is required instead. The previous behavior "tricking" the cast operator was undocumented and confusing.
- The
IRef.GetTarget
andIRef.GetTargetOrNull
methods have been moved to extension methods, which could require you to add newusing
directives in your code. - In
Metalama.Patterns.Wpf
, there are a few changes with the[Command]
aspect:- the DelegateCommand type has been moved to the
Metalama.Patterns.Wpf
namespace, - the aspect generates properties of type DelegateCommand, DelegateCommand<T>, AsyncDelegateCommand or AsyncDelegateCommand<T> instead of ICommand. All these types implement the ICommand interface, but the
Execute(object)
method is now implemented privately. It is replaced by a strongly-typed methodExecute()
for parameterless commands orExecute(T)
for commands accepting a parameter.
- the DelegateCommand type has been moved to the