Open sandboxFocusImprove this doc

Metalama 2025.0

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 collections
  • ref/unsafe in iterators and async methods
  • ref 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 and ToString 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 of ReferenceResolutionOptions in IRef.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 and IRef.GetTargetOrNull methods have been moved to extension methods, which could require you to add new using directives in your code.
  • In Metalama.Patterns.Wpf, there are a few changes with the [Command] aspect: