Open sandboxFocusImprove this doc

What's New in PostSharp 2027.0

The highlights of PostSharp 2027.0 are:

  • Faster builds of .NET projects. With dotnet build, PostSharp compiles .NET projects in a background build server that keeps the compiler loaded between builds, on Windows, Linux and macOS. Each compilation is several times faster. Until now, only .NET Framework projects benefited from such a server. See Background build server for .NET.
  • Faster aspects at run time. With advice parameter binding, now a public feature, advices receive typed parameters instead of an object allocated on each call, so the woven code computes only the values that the advices need and allocates no object for them. See Advice parameter binding.
  • Dependency injection in aspects. An aspect can receive a service from dependency injection: the target class gets the service from its constructors, and the advices read it. The caching aspects are the first to use it, so each container can have its own cache. See Dependency injection in aspects.
  • Support for .NET 11 and C# 15. PostSharp supports the .NET 11 SDK, C# 15 and runtime async. See Support for .NET 11 and C# 15.
  • Cross-platform options interface and command line tool. A browser-based user interface and the postsharp command line tool replace the Windows options application. See Cross-platform options interface and command line tool.
  • More than 100 bug fixes. PostSharp 2027.0 fixes more than 100 bugs in the compiler, the aspect framework and the Pattern Libraries. Many of them come from the deep backlog, and about a third were found by AI agents. See Bug fixes.
Note

This release includes several breaking changes. Refer to Breaking Changes in PostSharp 2027.0 for more detailed information.

Background build server for .NET

Until PostSharp 2026.0, only .NET Framework projects could use the pipe server, a background process that keeps the compiler loaded between builds. Each compilation of a .NET project started a process of its own, which loaded and compiled the PostSharp compiler again.

PostSharp 2027.0 adds a pipe server for .NET. When you build with the .NET SDK (dotnet build), the compilations of your .NET and .NET Standard projects run in one background process, on Windows, Linux and macOS. The compiler stays loaded and compiled between projects and between builds, and the Pattern Libraries of the same version as PostSharp are loaded once. Each compilation loads the assemblies of the project into an assembly load context of its own, which is unloaded when the compilation ends, so the compilations do not affect each other. In our measurements on small projects, a compilation took about 0.25 seconds with a running server instead of about 1 second in a process of its own.

  • The server is used by default. Set the PostSharpUsePipeServer MSBuild property to False to compile each project in a process of its own, as before.
  • The MSBuild of Visual Studio still compiles .NET projects in a process of their own. A compilation for x86 never uses a pipe server, because a 32-bit process does not have enough address space for a server.
  • The server exits after 15 minutes without a build. postsharp shutdown stops it at once.
  • The server reads the assemblies of your projects into memory, so it does not lock their files.

To compile each project in a process of its own, for instance on a build agent, set the property in the project or in a Directory.Build.props file:

<PropertyGroup>
  <PostSharpUsePipeServer>False</PostSharpUsePipeServer>
</PropertyGroup>

Build-time code that keeps a reference to a type of the project in a static field of a shared library keeps the whole compilation in memory, and shares its state with the other projects that the server compiles. The new ProjectLocal<T> class stores build-time state per project instead. See Diagnosing Memory Leaks of In-Process Compilations to detect and diagnose such leaks, including the PostSharpVerifyLoadContextUnload property, which makes the build fail when a compilation stays in memory.

Advice parameter binding

Until now, the advices of an aspect received their context through an AdviceArgs object, such as MethodExecutionArgs. This object is allocated on the heap on each call, and it gives access to the arguments and the return value as object values, which the aspect must cast.

With advice parameter binding, an advice method declares the values it needs as typed parameters. A custom attribute on each parameter selects the value: an argument, the instance, the return value, the exception, a variable shared between advices, the reflection object of the target, and so on. The woven code computes only these values and allocates no object.

The following aspect measures the duration of a method:

[PSerializable]
public sealed class ProfileAttribute : MethodLevelAspect
{
    [OnMethodEntryAdvice]
    public void OnEntry( [State( StateScope.MethodInvocation )] out Stopwatch stopwatch )
    {
        stopwatch = Stopwatch.StartNew();
    }

    [OnMethodExitAdvice]
    public void OnExit( [DeclarationName( IncludeTypeName = true )] string methodName,
                        [State( StateScope.MethodInvocation )] Stopwatch stopwatch )
    {
        Console.WriteLine( "{0} took {1} ms.", methodName, stopwatch.ElapsedMilliseconds );
    }
}

The Pattern Libraries already used this mechanism internally. PostSharp 2027.0 makes it public, with the following improvements:

  • PostSharp checks the bound parameters against each target at build time. An advice that does not fit a target causes an error with a message code, instead of a compiler crash or invalid code.
  • New bindings give an advice the exception ([Exception]), the return value in the method boundary advices ([ReturnValue]), and the reflection object of the target ([Declaration]).
  • Advice methods can be generic. PostSharp infers the type arguments for each target, so values of value types are not boxed.
  • [CurrentTask] gives the task that the caller of an async method observes, also for methods that return ValueTask.
  • OnMethodInvokeAsyncAdvice accepts the [Declaration], [DeclarationName], [DeclarationIdentifier], [Arguments], [Argument] and [Binding] parameters, also in a generic advice method, and can return a Task<T> whose result becomes the result of the target method.
  • Advices are grouped without a master advice. PostSharp infers the groups of each class: an advice joins the group of its class that has no advice of its kind yet. The new Group property selects a group by name when the inference is ambiguous, also across the classes of an aspect, and the pointcut and the properties of a group can be set on any of its advices. See Selecting Members with Pointcuts.
  • [SelfPointcut] and the Master property are obsolete. A group without pointcut applies to the target of the aspect, so [SelfPointcut] is redundant; set Group instead of Master. This is a breaking change: several advices of different kinds that each had [SelfPointcut] were separate groups, and are now one group, which shares MethodExecutionArgs and its MethodExecutionTag. See Breaking Changes in PostSharp 2027.0.

The documentation of composite aspects has been reorganized around this feature. See Developing Member-Level Aspects, Advice Parameters and Developing Type- and Assembly-Level Aspects. To convert an existing aspect, see Modernizing Your Custom Aspects.

Dependency injection in aspects

An aspect can now receive a service from dependency injection, for instance from Microsoft.Extensions.DependencyInjection. The aspect returns an ImportDependencyAdviceInstance from ProvideAdvices(object), and PostSharp adds a field and a constructor parameter of the service type to the target class and to its derived classes in the project. The advices read the service with ImportMemberAttribute. The original constructors are kept as non-public forwarders, so objects created with new still compile: they get the service from the new RunTimeServiceLocator class. Since this adds a parameter to the public constructors of the target class, it changes the public API of the class. See Receiving Dependencies from the Constructors of the Target Class.

The following aspect receives three services. The target class gets them from its constructors, and the advice reads them with ImportMemberAttribute:

[PSerializable]
public sealed class AuditedAttribute : MethodLevelAspect, IAdviceProvider
{
    public IEnumerable<AdviceInstance> ProvideAdvices( object targetElement )
    {
        // Required: the constructor fails when the service is not available.
        yield return new ImportDependencyAdviceInstance( typeof(IAuditLog) );

        // Optional: the dependency can be null.
        yield return new ImportDependencyAdviceInstance( typeof(IMetrics) ) { IsRequired = false };

        // Lazy: the service is resolved when the advice first uses it.
        yield return new ImportDependencyAdviceInstance( typeof(INotifier) ) { IsLazy = true };
    }

    [OnMethodSuccessAdvice]
    public static void OnSuccess( [Declaration] MethodBase method,
                                  [ImportMember] IAuditLog auditLog,
                                  [ImportMember] IMetrics? metrics,
                                  [ImportMember] INotifier notifier )
    {
        string name = method.DeclaringType!.Name + "." + method.Name;

        auditLog.Write( "Executed " + name + "." );
        metrics?.Increment( name );
        notifier.Notify( name + " succeeded." );
    }
}

Caching with dependency injection

The classes with cached methods can now receive their cache from a dependency injection container. With CacheConfiguration.UseDependencyInjection set to true, their constructors receive an ICachingService, which the application registers in the container with its backend and its profiles. Each container then has its own cache. The methods that do not receive a service use DefaultService, which replaces DefaultBackend and DefaultKeyBuilder. Static methods and methods of structs cannot receive a service, so they use DefaultService and cause the warning CAC011, and invalidating a method that uses another caching service causes CAC012. See Using Caching with Dependency Injection and Breaking Changes in PostSharp 2027.0.

The following class receives an ICachingService from the container:

[CacheConfiguration( UseDependencyInjection = true )]
public class ProductCatalog
{
    private readonly IProductRepository repository;

    // The build appends an ICachingService parameter to this constructor.
    public ProductCatalog( IProductRepository repository )
    {
        this.repository = repository;
    }

    [Cache( IgnoreThisParameter = true )]
    public Product GetProduct( int id ) => this.repository.Load( id );

    [InvalidateCache( nameof(GetProduct) )]
    public void RenameProduct( int id, string name ) => this.repository.Rename( id, name );
}

Members and fields of the target class

The advices of any aspect, including a statically scoped aspect such as a MethodLevelAspect, can now work with the state of the target object, which previously required an InstanceLevelAspect:

The following method-level aspect counts the calls of a method in a field of the target object, and reads a property and a method of this object:

[PSerializable]
public sealed class CountCallsAttribute : MethodLevelAspect
{
    [OnMethodEntryAdvice]
    public static void OnEntry<T>( [This] T instance,
                                   [ImportMember( "callCount", IsRequired = true )] ref int callCount,
                                   [ImportMember( "Name" )] string name,
                                   [ImportMember( "OnCalled" )] Action<T, int> onCalled )
    {
        callCount++;
        Console.WriteLine( "{0}: call {1}.", name, callCount );
        onCalled?.Invoke( instance, callCount );
    }
}

Support for .NET 11 and C# 15

PostSharp 2027.0 supports the .NET 11 SDK and C# 15, MSBuild 18.9 and 18.10, and Visual Studio 18.9 and 18.10. The supported target frameworks move to .NET 10 and .NET 11, and the .NET 10 SDK is the minimum to build. See Breaking Changes in PostSharp 2027.0.

Runtime async

With <Features>runtime-async=on</Features>, the C# compiler implements async methods without a state machine, and the runtime suspends and resumes them. PostSharp 2027.0 applies aspects to these methods. The advices run at the same points and receive the same values as with a state machine. The only difference is that [CurrentTask] is null, because the runtime provides no task while the method runs.

The following project targets .NET 11 and enables runtime async:

<PropertyGroup>
  <TargetFramework>net11.0</TargetFramework>
  <Features>runtime-async=on</Features>
</PropertyGroup>

Cross-platform options interface and command line tool

The Windows options application is replaced by a browser-based user interface and by the postsharp command line tool. Both are built on the infrastructure engine of Metalama, which is mature and cross-platform, so they work the same on Windows, Linux and macOS.

Install the tool as a .NET global tool:

dotnet tool install -g PostSharp.Tool

The tool registers license keys, edits the configuration of PostSharp, and reports the state of telemetry:

postsharp license register <key>
postsharp license list
postsharp ui
postsharp shutdown

postsharp ui opens the configuration pages in your browser. On Windows, the toast notifications that PostSharp shows after a build, for instance when a license is about to expire, open the same pages. postsharp shutdown stops the PostSharp processes that keep running after a build, such as the pipe server. See Deploying License Keys.

PostSharp 2027.0 also accepts license keys signed with the Elliptic Curve Digital Signature Algorithm (ECDSA), which can be verified on every platform, including macOS with .NET 11. The license keys issued before remain valid.

Telemetry

Usage reporting (telemetry) is now switched on at the first build, unless you opt out, as in Metalama. A notification informs you when this happens. Crash reports are still sent only after you agree.

To opt out on a machine, set the POSTSHARP_TELEMETRY_OPT_OUT environment variable. To opt out for all the builds of a repository, set the TelemetryEnabled property in the postsharp.config file at the root of the repository:

<Project xmlns="http://schemas.postsharp.org/1.0/configuration">
  <Property Name="TelemetryEnabled" Value="False" />
</Project>

Build integration

  • PostSharp supports the MSBuild server, which the .NET 11 SDK enables by default.
  • An unattended build, for instance on a build server, is now licensed for the production mode of PostSharp Logging, so the build server needs no license key for it. See Using PostSharp on a Build Server.
  • TreatWarningsAsErrors now applies to the warnings of PostSharp and of aspects. See Ignoring and Escalating Warnings.
  • The compiler no longer shares its third-party dependencies, such as Newtonsoft.Json, System.Text.Json or System.Collections.Immutable, with the project it compiles. On .NET Framework, a project that references a higher version of one of them no longer fails with PS0264, and the build-time code of the project runs against the versions that the project references. See Breaking Changes in PostSharp 2027.0.
  • Add-ins compile against the reference assemblies of the new PostSharp.Sdk package.

To keep the warnings of PostSharp as warnings in a project that treats the warnings of the compiler as errors, set PostSharpTreatWarningsAsErrors to False:

<PropertyGroup>
  <TreatWarningsAsErrors>True</TreatWarningsAsErrors>
  <PostSharpTreatWarningsAsErrors>False</PostSharpTreatWarningsAsErrors>
</PropertyGroup>

Aspect test framework

The new PostSharp.Testing.AspectTesting package is a test framework for aspects, based on xunit.v3. Each source file of a test project is a test: the framework compiles it, weaves it with PostSharp in an assembly of its own, verifies the woven code with ILVerify, and runs the program. It can also compare the output of the program and the errors and warnings of its build with expected files. A test can therefore check the errors that an aspect emits when it is misused, as well as its run-time behavior. For details, see Testing Aspects with the Aspect Test Framework.

With the @CompareTransformedCode directive, a test also checks the code that the aspect generates. The framework decompiles the woven assembly to C# with ICSharpCode.Decompiler, the decompiler engine of ILSpy, and compares the declarations marked with // <target>, or all the top-level types of the file, with a .t.cs snapshot stored next to the test. As with the other expected files, you accept a new snapshot when the woven code changes intentionally.

The following test applies a logging aspect to a method and calls it:

public static class Calculator
{
    [Log]
    public static int Divide( int a, int b ) => a / b;
}

public static class Program
{
    public static void Main()
    {
        Calculator.Divide( 10, 2 );

        try
        {
            Calculator.Divide( 1, 0 );
        }
        catch ( DivideByZeroException )
        {
        }
    }
}

The test passes when the program writes the expected output, which is stored next to the test:

Entering Divide(10, 2).
Divide returned 5.
Entering Divide(1, 0).
Divide failed: Attempted to divide by zero.

Plugin for AI coding agents

PostSharp 2027.0 provides a plugin for AI coding agents. The plugin supports Claude Code and OpenAI Codex. It contains this documentation, the API reference, and scripts that look up an article or an API. The agent can therefore check the real API before it writes PostSharp code. The plugin also instructs the agent to build the project to verify the code.

Install the plugin from the PostSharp.AI.Skills marketplace. For instructions, see Configuring AI Coding Agents.

In Claude Code, for instance, add the marketplace and install the plugin:

/plugin marketplace add https://github.com/postsharp/PostSharp.AI.Skills
/plugin install postsharp

Bug fixes

PostSharp 2027.0 fixes more than 100 bugs, not counting the issues of its own build and tests. Many of these bugs come from the deep backlog, and about a third of them were found by AI agents. The fixes cover the compiler, the aspect framework, the build integration, licensing, and the Pattern Libraries, in particular the caching, logging, threading and model libraries. For the complete list, see the release notes of each build on GitHub.