Metalama 2025.0 focuses on two areas: ensuring compatibility with the latest .NET stack and completing gaps left in the previous version, particularly in support for type introductions. We've also implemented minor improvements requested by the community.
Support for .NET 9.0 and C# 13
C# 13 features
Metalama 2025.0 supports all features of C# 13:
paramscollectionsref/unsafein iterators and async methodsref structcan implement interfaces- Classes can have
ref structconstraints - New escape character in strings
- Locking on the Lock class
- Implicit indexer access in object initializers
- Overload resolution priority
partialproperties
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
Source generators now execute after any Metalama transformation. Previously, code generators executed before Metalama at build time, causing inconsistencies with the design-time experience because Metalama wouldn't "see" the output of source generators.
Aspects can now introduce code that relies on the GeneratedRegex attribute to use build-time-generated regular expressions.
You can also use Roslyn interceptors side-by-side with Metalama since they no longer conflict with 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 implement generic interfaces bound to a type parameter of the target type. For instance, you can build an Equatable aspect that generates code for the IEquatable<T> interface, even for introduced types.
T# improvements
Dynamic definition of local variables
You can now 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 expressions 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.
Set the partial keyword 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, mark the template member as extern to satisfy the C# compiler.
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:
ToDisplayStringandToStringimplemented 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
ReferenceResolutionOptionsenum and all parameters ofReferenceResolutionOptionsinIRef.GetTargethave 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.GetTargetandIRef.GetTargetOrNullmethods have been moved to extension methods, which could require you to add newusingdirectives 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.Wpfnamespace. - 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's replaced by a strongly-typed methodExecute()for parameterless commands orExecute(T)for commands accepting a parameter.
- The DelegateCommand type has been moved to the