<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="https://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>PostSharp Blog</title>
    <description>News, tips and tricks from PostSharp</description>
    <link>https://postsharp.net</link>
    <atom:link href="https://postsharp.net/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 30 Oct 2026 09:51:12 +0000</pubDate>
    <lastBuildDate>Fri, 30 Oct 2026 09:51:12 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
    
      <item>
        <title>Intercepting Calls to Any Method with Metalama Interceptors</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-10-interceptors/metalama.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;Metalama 2027.0 adds call-site interceptors. An override aspect modifies the called method itself, for every caller, and requires its source code. An interceptor modifies the call sites instead: an aspect or a fabric selects the calls to a method within a given scope, and Metalama rewrites them at build time to target the interceptor. The called method can therefore belong to any assembly: the .NET libraries, a NuGet package, or another team’s project. The interceptor is either a hand-written C# method or generated from a T# template for each signature.&lt;/p&gt;

&lt;h2 id="a-quick-example-retrying-calls-to-a-third-party-service"&gt;A quick example: retrying calls to a third-party service&lt;/h2&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;ShippingClient&lt;/code&gt; is a class of a vendor SDK. Its methods sometimes throw a &lt;code class="language-plaintext highlighter-rouge"&gt;TimeoutException&lt;/code&gt;, and the application calls them from many places:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShipmentService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ShippingClient&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;EstimateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetStatusAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;trackingNumber&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTrackingStatusAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;trackingNumber&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Ship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateShipment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The following project fabric retries the calls to the methods of &lt;code class="language-plaintext highlighter-rouge"&gt;ShippingClient&lt;/code&gt; whose name starts with &lt;code class="language-plaintext highlighter-rouge"&gt;Get&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fabric&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ProjectFabric&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AmendProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IProjectAmender&lt;/span&gt; &lt;span class="n"&gt;amender&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;amender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InterceptMethods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ShippingClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllMethods&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"Get"&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RedirectToSynthesizedMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;Retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CompileTime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Proceed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;TimeoutException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;$"Attempt &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Retrying."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Retry&lt;/code&gt; is a T# template: C# code in which &lt;code class="language-plaintext highlighter-rouge"&gt;meta.Proceed()&lt;/code&gt; stands for the original call. From this template, Metalama generates one interceptor per intercepted method:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MetalamaInterceptors&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;ShippingClient_GetQuote_Interceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;ShippingClient&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;TimeoutException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;$"Attempt &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Retrying."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ShippingClient_GetTrackingStatusAsync_Interceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;ShippingClient&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;trackingNumber&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTrackingStatusAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;trackingNumber&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;TimeoutException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;$"Attempt &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Retrying."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The source code of &lt;code class="language-plaintext highlighter-rouge"&gt;ShipmentService&lt;/code&gt; is unchanged; its calls to &lt;code class="language-plaintext highlighter-rouge"&gt;GetQuote&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;GetTrackingStatusAsync&lt;/code&gt; are routed to these methods. The compile-time parameter &lt;code class="language-plaintext highlighter-rouge"&gt;maxAttempts&lt;/code&gt; became the constant &lt;code class="language-plaintext highlighter-rouge"&gt;3&lt;/code&gt;, and the interceptor of the async method awaits the call, so the retry covers the whole operation. &lt;code class="language-plaintext highlighter-rouge"&gt;CreateShipment&lt;/code&gt; is not intercepted, because retrying an operation that is not idempotent could create the shipment twice.&lt;/p&gt;

&lt;h2 id="compared-with-c-interceptors-and-roslyn-source-generators"&gt;Compared with C# interceptors and Roslyn source generators&lt;/h2&gt;

&lt;p&gt;C# has its own interceptors: a method marked with &lt;code class="language-plaintext highlighter-rouge"&gt;[InterceptsLocation]&lt;/code&gt; replaces the call at one location in the source code, and a Roslyn source generator writes these methods. Metalama interceptors are unrelated to this feature, although both rewrite calls at build time.&lt;/p&gt;

&lt;p&gt;Compared with C# interceptors, Metalama interceptors are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Much simpler.&lt;/strong&gt; No low-level Roslyn code generator is needed to walk syntax trees, query the semantic model and emit interceptors as C# text. Metalama locates the call sites and generates the interceptors, as in the retry example above.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No additional project.&lt;/strong&gt; A source generator must live in its own project, referenced as an analyzer. The fabric and the interceptors can live in the same project as the business code.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Template-based.&lt;/strong&gt; The interceptor is a T# template: C# that looks like the code it generates, with &lt;code class="language-plaintext highlighter-rouge"&gt;meta.Proceed()&lt;/code&gt; for the original call. One template covers every signature, including async and generic methods.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Able to intercept delegates.&lt;/strong&gt; Method groups converted to delegates, such as &lt;code class="language-plaintext highlighter-rouge"&gt;items.Select( Normalize )&lt;/code&gt;, and event handlers can be intercepted, in addition to calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each approach has its use cases:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Use case&lt;/th&gt;
      &lt;th&gt;Better tool&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Retrying, timing or auditing the calls to an API&lt;/td&gt;
      &lt;td&gt;Metalama&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tracing calls temporarily, or only in some builds&lt;/td&gt;
      &lt;td&gt;Metalama&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Adding test seams to legacy code, such as faking &lt;code class="language-plaintext highlighter-rouge"&gt;Thread.Sleep&lt;/code&gt; or the file system&lt;/td&gt;
      &lt;td&gt;Metalama&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Moving PostSharp call-site interception to Metalama&lt;/td&gt;
      &lt;td&gt;Metalama&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Generating optimized code from the arguments of each call, such as a lambda, a query or a SQL string, as Dapper.AOT and EF Core precompiled queries do&lt;/td&gt;
      &lt;td&gt;Roslyn source generator&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A library that ships interceptors to its users without adding a dependency&lt;/td&gt;
      &lt;td&gt;Roslyn source generator&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id="features"&gt;Features&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Hand-written or template-based interceptors.&lt;/strong&gt; Calls can be redirected to an existing static method, which can be debugged and unit-tested like any C# code, or to interceptors generated from a T# template, one for each intercepted signature.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Generic interceptors.&lt;/strong&gt; A generic template covers methods of the same shape whose parameters have different types, such as &lt;code class="language-plaintext highlighter-rouge"&gt;SaveOrder( Order order )&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;SaveCustomer( Customer customer )&lt;/code&gt;. Calls to generic methods, such as &lt;code class="language-plaintext highlighter-rouge"&gt;Serialize&amp;lt;T&amp;gt;( T value )&lt;/code&gt;, are handled either by one interceptor per type argument or by one shared generic interceptor.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Call-site context.&lt;/strong&gt; An interceptor can receive the calling member, the calling object, a parameter of the calling method, and the caller information attributes (&lt;code class="language-plaintext highlighter-rouge"&gt;[CallerMemberName]&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;[CallerFilePath]&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;[CallerLineNumber]&lt;/code&gt;) resolved to the intercepted call.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Additional parameter values.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;WithArgs&lt;/code&gt; assigns values to the parameters of the interceptor that the intercepted method does not have: constants such as literals, enumeration values and types, or values computed for each call site. For templates, it also sets the compile-time parameters.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Async and iterator methods.&lt;/strong&gt; The interceptor of an awaitable method is &lt;code class="language-plaintext highlighter-rouge"&gt;async&lt;/code&gt; and awaits the call; the interceptor of an async iterator is an async iterator.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Delegates and event handlers.&lt;/strong&gt; Method groups converted to delegates and event subscriptions are intercepted along with calls, and can be filtered by kind of use.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Interface and virtual calls.&lt;/strong&gt; Calls bound to overrides of the selected methods are intercepted, and calls bound to implementations of interface methods can be included.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Flexible scope.&lt;/strong&gt; A registration applies to the target type of an aspect, a namespace, a project, the types that a query selects, some build configurations only, or every project that references a library.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Configurable placement.&lt;/strong&gt; Interceptors are generated in a shared static class, in a class of your choice, or in the calling type, where they can access its private members and the protected methods of its base classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="redirecting-calls-to-a-hand-written-method"&gt;Redirecting calls to a hand-written method&lt;/h2&gt;

&lt;p&gt;The simplest interceptor is a hand-written static method. Being plain C#, it can be debugged and unit-tested like any other code.&lt;/p&gt;

&lt;p&gt;The following method audits the deletion of files. The &lt;code class="language-plaintext highlighter-rouge"&gt;[ExcludeInterceptors]&lt;/code&gt; attribute prevents its own call to &lt;code class="language-plaintext highlighter-rouge"&gt;File.Delete&lt;/code&gt; from being intercepted, which would cause infinite recursion:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ExcludeInterceptors&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Audit&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DeleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CallerMemberName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;caller&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CallerLineNumber&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (line &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) deletes &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFileName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A project fabric redirects every call to &lt;code class="language-plaintext highlighter-rouge"&gt;File.Delete&lt;/code&gt; in the project to this method, and passes a constant value for &lt;code class="language-plaintext highlighter-rouge"&gt;category&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fabric&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ProjectFabric&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AmendProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IProjectAmender&lt;/span&gt; &lt;span class="n"&gt;amender&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;interceptor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsINamedType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Methods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OfName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;amender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InterceptMethods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RedirectToExistingMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;interceptor&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"FileSystem"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The calls in the business code are rewritten at build time:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportCleaner&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DeleteReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".pdf"&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"FileSystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"DeleteReport"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DeleteDraft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".draft"&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"FileSystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"DeleteDraft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each parameter of the interceptor is bound to a value of the call site. For instance methods, the first parameter receives the receiver of the call. The remaining parameters are matched by name rather than position. Parameters with a caller information attribute, such as &lt;code class="language-plaintext highlighter-rouge"&gt;[CallerMemberName]&lt;/code&gt;, receive the location of the intercepted call.&lt;/p&gt;

&lt;h2 id="generating-interceptors-from-a-template"&gt;Generating interceptors from a template&lt;/h2&gt;

&lt;p&gt;Beyond &lt;code class="language-plaintext highlighter-rouge"&gt;meta.Proceed()&lt;/code&gt;, templates can inspect the interception they implement through &lt;code class="language-plaintext highlighter-rouge"&gt;meta.Interception&lt;/code&gt;. For instance, the following line prints the name of the intercepted method:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;$"Calling &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InterceptedMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By default, call sites that need the same code share one generated interceptor in an internal class named &lt;code class="language-plaintext highlighter-rouge"&gt;MetalamaInterceptors&lt;/code&gt;. A template that depends on the caller must declare it with &lt;code class="language-plaintext highlighter-rouge"&gt;WithGranularity&lt;/code&gt;: the &lt;code class="language-plaintext highlighter-rouge"&gt;Member&lt;/code&gt; granularity exposes the calling member, and the &lt;code class="language-plaintext highlighter-rouge"&gt;CallSite&lt;/code&gt; granularity the call site itself. A finer granularity generates more methods.&lt;/p&gt;

&lt;p&gt;The location of the generated interceptor is also configurable. With &lt;code class="language-plaintext highlighter-rouge"&gt;PlaceInCallingType&lt;/code&gt;, it becomes a private method of the calling type, which does not need to be &lt;code class="language-plaintext highlighter-rouge"&gt;partial&lt;/code&gt;. The template can then access the private members of the caller, such as its logger:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LogPaymentCallsAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TypeAspect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;BuildAspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IAspectBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INamedType&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InterceptMethods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentGateway&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllMethods&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RedirectToSynthesizedMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogCall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PlaceInCallingType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;LogCall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThisType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;$"Calling &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InterceptedMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Proceed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Applied to &lt;code class="language-plaintext highlighter-rouge"&gt;OrderService&lt;/code&gt;, the aspect generates this method inside the class, next to its &lt;code class="language-plaintext highlighter-rouge"&gt;_log&lt;/code&gt; field:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;PaymentGateway_Charge_Interceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"Calling Charge."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PaymentGateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="scoping-interceptors"&gt;Scoping interceptors&lt;/h2&gt;

&lt;p&gt;The scope of an interception is determined by where it is registered:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;in an aspect, the calls written in the target type of the aspect;&lt;/li&gt;
  &lt;li&gt;in a namespace fabric, the calls written in its namespace;&lt;/li&gt;
  &lt;li&gt;in a project fabric, the calls written in the whole project, or in the types that a query selects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fabric can also register interceptors only in some builds. The following fabric does nothing unless the &lt;code class="language-plaintext highlighter-rouge"&gt;TRACE_CALLS&lt;/code&gt; symbol is defined:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;amender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PreprocessorSymbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"TRACE_CALLS"&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For finer control, &lt;code class="language-plaintext highlighter-rouge"&gt;ForEachSite&lt;/code&gt; makes a decision for each call site. It can, for instance, pass a parameter of the calling method to the interceptor, or skip the call sites where that parameter does not exist.&lt;/p&gt;

&lt;h2 id="try-it"&gt;Try it&lt;/h2&gt;

&lt;p&gt;Interceptors are available from Metalama 2027.0.4-preview, in the new &lt;code class="language-plaintext highlighter-rouge"&gt;Metalama.Extensions.Interceptors&lt;/code&gt; package, which works with a free Metalama Community license. This is a preview release and is not supported in production.&lt;/p&gt;

&lt;p&gt;The documentation covers the feature in depth:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://doc.postsharp.net/metalama/preview/conceptual/interceptors"&gt;Interceptors&lt;/a&gt; for an overview and the full API.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://doc.postsharp.net/metalama/preview/conceptual/interceptors/use-cases"&gt;When to Use Interceptors&lt;/a&gt; for their use cases, their limitations, and a comparison with other techniques.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href="https://doc.postsharp.net/metalama/preview/conceptual/release-notes/release-notes-2027-0"&gt;Metalama 2027.0 release notes&lt;/a&gt; for the rest of the release. We welcome your feedback on &lt;a href="https://github.com/metalama/Metalama/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy meta-programming with Metalama!&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/metalama-interceptors"&gt;Intercepting Calls to Any Method with Metalama Interceptors&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Thu, 29 Oct 2026 07:00:00 +0000</pubDate>
        
        <link>https://postsharp.net/blog/metalama-interceptors</link>
        <guid isPermaLink="true">https://postsharp.net/blog/metalama-interceptors</guid>
        
        
        <category>Metalama</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-10-interceptors/metalama.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>AI Coding Agent Support in PostSharp 2027.0</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-10-ai-coding-agents/postsharp.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;An AI coding agent in a PostSharp project works half blind. The source of a method shows none of the logging, caching or thread safety that PostSharp weaves into the assembly. When the agent writes an aspect, it rebuilds the API from memory, out of samples that span twenty years of PostSharp versions. And when the build fails, it is tempted to delete the aspect.&lt;/p&gt;

&lt;p&gt;PostSharp 2027.0 gives the agent the facts: the real API, what the build actually did, and tests to check the result. Here is what we added:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="#postsharp-ai-plugin-for-claude-code-and-codex"&gt;PostSharp AI Plugin for Claude Code and Codex&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#cli-commands-to-introspect-aspects-without-visual-studio"&gt;CLI Commands to Introspect Aspects Without Visual Studio&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#documentation-of-every-build-message"&gt;Documentation of Every Build Message&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#aspect-test-framework"&gt;Aspect Test Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="postsharp-ai-plugin-for-claude-code-and-codex"&gt;PostSharp AI Plugin for Claude Code and Codex&lt;/h2&gt;

&lt;p&gt;The PostSharp plugin for Claude Code and OpenAI Codex gives the agent the documentation it needs to work with PostSharp. The plugin contains all the articles in Markdown format, the API reference of every namespace, two scripts that find an article or an API, and compilable source code of all examples. It also instructs the agent to look up the API before writing code, to build to verify, and to fix the cause of a build message instead of silencing it.&lt;/p&gt;

&lt;p&gt;In Claude Code:&lt;/p&gt;

&lt;div class="language-text highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;/plugin marketplace add https://github.com/postsharp/PostSharp.AI.Skills
/plugin install postsharp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Available now from the &lt;a href="https://github.com/postsharp/PostSharp.AI.Skills"&gt;PostSharp.AI.Skills&lt;/a&gt; marketplace. See &lt;a href="https://doc.postsharp.net/postsharp/preview/deployment/ai-agents"&gt;Configuring AI Coding Agents&lt;/a&gt;, which also covers Codex.&lt;/p&gt;

&lt;h2 id="cli-commands-to-introspect-aspects-without-visual-studio"&gt;CLI Commands to Introspect Aspects Without Visual Studio&lt;/h2&gt;

&lt;p&gt;The &lt;code class="language-plaintext highlighter-rouge"&gt;postsharp introspect&lt;/code&gt; commands tell, from the files of the last build, which aspects apply to a declaration, what created them, and what they changed, in which order.&lt;/p&gt;

&lt;div class="language-powershell highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;postsharp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;introspect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Orders/Repository.cs:10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The output, abridged:&lt;/p&gt;

&lt;div class="language-json highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M:App.Orders.Repository.Save(App.Customer)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Method"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Orders/Repository.cs:10:21"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"targetFrameworks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"net10.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aspectInstances"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"truncated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f50384e0ebfe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aspectClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lib.LogAttribute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"shortName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Log"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"predecessors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"truncated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0374f134812d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Multicast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"isKindInferred"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A:App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Assembly"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"transformations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"truncated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"targetDeclaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M:App.Orders.Repository.Save(App.Customer)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Method"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wrapped by advice(s) OnEntry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"linesOfCodeAvoided"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The agent learns that the &lt;code class="language-plaintext highlighter-rouge"&gt;Log&lt;/code&gt; aspect applies to &lt;code class="language-plaintext highlighter-rouge"&gt;Save&lt;/code&gt; because of a multicast attribute on the assembly, which the source of &lt;code class="language-plaintext highlighter-rouge"&gt;Save&lt;/code&gt; does not show.&lt;/p&gt;

&lt;p&gt;We designed the commands for agents first:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;JSON by default&lt;/strong&gt;, with a stable schema. &lt;code class="language-plaintext highlighter-rouge"&gt;--format text&lt;/code&gt; is for people.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;OData queries.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;list&lt;/code&gt; filters aspect classes, aspect instances, transformations and declarations with &lt;code class="language-plaintext highlighter-rouge"&gt;$filter&lt;/code&gt; expressions, such as &lt;code class="language-plaintext highlighter-rouge"&gt;--where "aspectClass/shortName eq 'Log'"&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Output that fits in a context window.&lt;/strong&gt; Lists are capped and summarized by namespace, and each truncated list gives the command that lists the rest.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;batch&lt;/code&gt;&lt;/strong&gt; runs several queries over one load of the projects, several times faster than separate calls.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Backward compatible.&lt;/strong&gt; The commands read projects built with PostSharp 2023.0 to 2026.0, on Windows, Linux and macOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Available from 2027.0.7-preview, in the &lt;code class="language-plaintext highlighter-rouge"&gt;PostSharp.Tool&lt;/code&gt; global tool:&lt;/p&gt;

&lt;div class="language-powershell highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PostSharp.Tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--prerelease&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;See &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/testing/introspect"&gt;Inspecting How Aspects Were Applied&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="documentation-of-every-build-message"&gt;Documentation of Every Build Message&lt;/h2&gt;

&lt;p&gt;The new &lt;a href="https://doc.postsharp.net/postsharp/preview/errors"&gt;Errors and Warnings reference&lt;/a&gt; has one article per message code, such as &lt;code class="language-plaintext highlighter-rouge"&gt;PS0215&lt;/code&gt;, with its cause and how to fix it. An agent that reads the message and then its article fixes the code instead of silencing the message.&lt;/p&gt;

&lt;p&gt;Available from 2027.0.5-preview.&lt;/p&gt;

&lt;h2 id="aspect-test-framework"&gt;Aspect Test Framework&lt;/h2&gt;

&lt;p&gt;The &lt;code class="language-plaintext highlighter-rouge"&gt;PostSharp.Testing.AspectTesting&lt;/code&gt; package, based on xunit.v3, is the framework we use to test PostSharp itself.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;One file, one test.&lt;/strong&gt; Each source file is compiled, woven in an assembly of its own, verified with ILVerify, and run.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Expected files.&lt;/strong&gt; The test compares the output of the program and the messages of its build with files stored next to it, so a test can expect a build error.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Snapshots of the woven code.&lt;/strong&gt; With &lt;code class="language-plaintext highlighter-rouge"&gt;@CompareTransformedCode&lt;/code&gt;, the framework decompiles the woven code to C# and compares it with a &lt;code class="language-plaintext highlighter-rouge"&gt;.t.cs&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A test applies the aspect and calls the method:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;Divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;DivideByZeroException&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It passes when the program writes the expected output:&lt;/p&gt;

&lt;div class="language-text highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;Entering Divide(10, 2).
Divide returned 5.
Entering Divide(1, 0).
Divide failed: Attempted to divide by zero.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Available from 2027.0.5-preview. See &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/testing/aspect-tests"&gt;Testing Aspects with the Aspect Test Framework&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="try-it"&gt;Try It&lt;/h2&gt;

&lt;p&gt;An agent writes boilerplate fast, but someone still reads every line it writes. An aspect keeps the pattern in one place, and the compiler applies it deterministically at every build, whoever wrote the code.&lt;/p&gt;

&lt;p&gt;These features are in preview releases, which are not supported in production. See &lt;a href="https://doc.postsharp.net/postsharp/preview/introduction/whats-new/2027-0"&gt;What’s New in PostSharp 2027.0&lt;/a&gt; for the rest of the release, and tell us how your agent did on &lt;a href="https://github.com/postsharp/PostSharp.Public/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy meta-programming with PostSharp!&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/postsharp-ai-coding-agents"&gt;AI Coding Agent Support in PostSharp 2027.0&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Wed, 28 Oct 2026 07:00:00 +0000</pubDate>
        
        <link>https://postsharp.net/blog/postsharp-ai-coding-agents</link>
        <guid isPermaLink="true">https://postsharp.net/blog/postsharp-ai-coding-agents</guid>
        
        
        <category>PostSharp</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-10-ai-coding-agents/postsharp.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>Moving PostSharp Aspects to Dependency Injection</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-10-dependency-injection/postsharp.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;PostSharp was designed when dependency injection was still young and the .NET Framework had no built-in container. .NET Core then built one in, around constructor injection, and constructor injection was what PostSharp aspects could not take part in. PostSharp 2027.0 changes that: an aspect can now ask for a service, and the class it enhances receives that service from its constructor, like any other dependency. You can move your legacy code to a DI-centric architecture and bring your aspects along.&lt;/p&gt;

&lt;h2 id="why-aspects-resisted-dependency-injection"&gt;Why aspects resisted dependency injection&lt;/h2&gt;

&lt;p&gt;Two properties of PostSharp aspects made them awkward to combine with a container:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;They are instantiated at build time.&lt;/strong&gt; The aspect object is created by the compiler, serialized into the assembly, and deserialized at run time. No container is involved, so no container can inject anything into it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;They usually have a static scope.&lt;/strong&gt; Unless it implements &lt;code class="language-plaintext highlighter-rouge"&gt;IInstanceScopedAspect&lt;/code&gt;, an aspect instance is stored in a static field, even when it is applied to an instance method. It is shared by all the objects of the class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the usual pattern was a global service locator, resolved when the aspect is initialized at run time:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSerializable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditedAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OnMethodBoundaryAspect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IAuditLog&lt;/span&gt; &lt;span class="n"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;RuntimeInitialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MethodBase&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;auditLog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ServiceLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuditLog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MethodExecutionArgs&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"Executed "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeclaringType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Caching was configured the same way, once for the whole process, at startup:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;CachingServices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultBackend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryCachingBackend&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This works, and it has run in production for years. It also has costs that grow once the rest of the application uses a container:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Hidden dependencies.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;OrderService&lt;/code&gt; needs an &lt;code class="language-plaintext highlighter-rouge"&gt;IAuditLog&lt;/code&gt;, but nothing in its constructor says so. You find out when the locator throws.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Initialization order.&lt;/strong&gt; The locator must be ready before the first enhanced class is used, which rules out the entry point and makes startup fragile.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;One global per process.&lt;/strong&gt; Two tests, or two hosts in the same process, share the same audit log and the same cache. Scoped services, such as a per-request &lt;code class="language-plaintext highlighter-rouge"&gt;DbContext&lt;/code&gt;, are out of reach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There was a second pattern, closer to dependency injection. An instance-scoped aspect introduced a property into the target class, copied an &lt;code class="language-plaintext highlighter-rouge"&gt;[Import]&lt;/code&gt; attribute onto it, and read it back with &lt;code class="language-plaintext highlighter-rouge"&gt;[ImportMember]&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSerializable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditedAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OnMethodBoundaryAspect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IInstanceScopedAspect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;IntroduceMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;Visibility&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Visibility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OverrideAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MemberOverrideAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ignore&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CopyCustomAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImportAttribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAuditLog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IAuditLog&lt;/span&gt; &lt;span class="n"&gt;AuditLog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ImportMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"AuditLog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IsRequired&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuditLog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AuditLogProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MethodExecutionArgs&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuditLogProperty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"Executed "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// CreateInstance and RuntimeInitializeInstance omitted.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This worked with MEF and the other attribute-based frameworks of the time, which set the properties of an object after creating it. It does not work with Microsoft.Extensions.DependencyInjection or any other constructor-based container: they pass every dependency to the constructor and never look at properties, and an aspect had no way to add a parameter to a constructor.&lt;/p&gt;

&lt;h2 id="pulling-dependencies-from-constructors"&gt;Pulling dependencies from constructors&lt;/h2&gt;

&lt;p&gt;This is what PostSharp 2027.0 adds: aspects themselves can pull their dependencies from the constructor. An aspect requests a service by returning an &lt;code class="language-plaintext highlighter-rouge"&gt;ImportDependencyAdviceInstance&lt;/code&gt; from &lt;code class="language-plaintext highlighter-rouge"&gt;IAdviceProvider.ProvideAdvices&lt;/code&gt;. PostSharp then adds a parameter of the service type to the constructors of the target class, and the advices receive the service through an &lt;code class="language-plaintext highlighter-rouge"&gt;[ImportMember]&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;Here is the same &lt;code class="language-plaintext highlighter-rouge"&gt;Audited&lt;/code&gt; aspect, extended to request three services: a required &lt;code class="language-plaintext highlighter-rouge"&gt;IAuditLog&lt;/code&gt;, an optional &lt;code class="language-plaintext highlighter-rouge"&gt;IMetrics&lt;/code&gt;, and an &lt;code class="language-plaintext highlighter-rouge"&gt;INotifier&lt;/code&gt; that is resolved only when it is first used:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSerializable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditedAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MethodLevelAspect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAdviceProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AdviceInstance&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProvideAdvices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;targetElement&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Required: the constructor fails when the service is not available.&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ImportDependencyAdviceInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAuditLog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Optional: the dependency can be null.&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ImportDependencyAdviceInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMetrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;IsRequired&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// Lazy: the service is resolved when the advice first uses it.&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ImportDependencyAdviceInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;IsLazy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OnMethodSuccessAdvice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Declaration&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;MethodBase&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ImportMember&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;IAuditLog&lt;/span&gt; &lt;span class="n"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ImportMember&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;IMetrics&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ImportMember&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;INotifier&lt;/span&gt; &lt;span class="n"&gt;notifier&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeclaringType&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"Executed "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" succeeded."&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The advice is a static method with bound parameters, as described in our &lt;a href="/blog/postsharp-advice-parameter-binding"&gt;previous post on advice parameter binding&lt;/a&gt;. The aspect remains statically scoped: it holds no service. The services live in the object that the container created.&lt;/p&gt;

&lt;p&gt;The class that uses the aspect declares only its own dependency:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IOrderRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IOrderRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Audited&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the composition root registers the services as usual:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuditLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConsoleAuditLog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMetrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConsoleMetrics&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConsoleNotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Microsoft.Extensions.DependencyInjection does not create Lazy&amp;lt;T&amp;gt; by itself.&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;serviceProvider.GetRequiredService&amp;lt;OrderService&amp;gt;()&lt;/code&gt; returns an &lt;code class="language-plaintext highlighter-rouge"&gt;OrderService&lt;/code&gt; with all four services. The notifier is created only when &lt;code class="language-plaintext highlighter-rouge"&gt;PlaceOrder&lt;/code&gt; succeeds for the first time.&lt;/p&gt;

&lt;h2 id="what-postsharp-generates"&gt;What PostSharp generates&lt;/h2&gt;

&lt;p&gt;The container can pass the services because the build changed the constructors of &lt;code class="language-plaintext highlighter-rouge"&gt;OrderService&lt;/code&gt;. Here is the woven class, decompiled and cleaned up (we renamed the generated fields and removed the compiler attributes):&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IOrderRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IAuditLog&lt;/span&gt; &lt;span class="n"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IMetrics&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// The original constructor, now a forwarder.&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IOrderRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// The enhanced constructor, the only public one.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IOrderRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;IAuditLog&lt;/span&gt; &lt;span class="n"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;IMetrics&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auditLog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auditLog&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;RunTimeServiceLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuditLog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;RunTimeServiceLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMetrics&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="n"&gt;RunTimeServiceLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="n"&gt;IAuditLog&lt;/span&gt; &lt;span class="n"&gt;AuditLog&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auditLog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="n"&gt;IMetrics&lt;/span&gt; &lt;span class="n"&gt;Metrics&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="n"&gt;INotifier&lt;/span&gt; &lt;span class="n"&gt;Notifier&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;AuditedAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="cm"&gt;/* the MethodBase of PlaceOrder */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notifier&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The transformation follows a few rules:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;One public constructor.&lt;/strong&gt; The original constructor keeps its body, attributes and parameters in the &lt;em&gt;enhanced constructor&lt;/em&gt;, which receives the services as extra parameters. The original signature becomes a &lt;code class="language-plaintext highlighter-rouge"&gt;protected internal&lt;/code&gt; &lt;em&gt;forwarder&lt;/em&gt;, so the container sees one public constructor and has no ambiguity to resolve.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The fields are assigned first.&lt;/strong&gt; The services are stored right after the call to the base constructor, before the body of your constructor runs, as C# would do for a field initialized from a parameter.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Your code wins.&lt;/strong&gt; If the class already has a field, a property or a constructor parameter of exactly the service type, PostSharp uses it and adds nothing. This is how you give the class itself access to the same service.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Derived classes follow.&lt;/strong&gt; The classes that derive from &lt;code class="language-plaintext highlighter-rouge"&gt;OrderService&lt;/code&gt;, in the same project or in another project that PostSharp processes, receive the dependencies of their base class, and their &lt;code class="language-plaintext highlighter-rouge"&gt;base(...)&lt;/code&gt; calls pass them. Derived classes can also read the services through the &lt;code class="language-plaintext highlighter-rouge"&gt;protected&lt;/code&gt; properties.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;One field per service.&lt;/strong&gt; When the aspect is applied to ten methods of the class, the class still gets one field and one constructor parameter per service type.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="migrate-at-your-own-pace"&gt;Migrate at your own pace&lt;/h2&gt;

&lt;p&gt;An application that has been in production for ten years does not move to a container in one commit. Some objects will come from the container long before the others stop using &lt;code class="language-plaintext highlighter-rouge"&gt;new&lt;/code&gt;. PostSharp supports both.&lt;/p&gt;

&lt;p&gt;When the source code calls &lt;code class="language-plaintext highlighter-rouge"&gt;new OrderService( repository )&lt;/code&gt;, it calls the forwarder, which passes &lt;code class="language-plaintext highlighter-rouge"&gt;null&lt;/code&gt; for each service. The enhanced constructor then gets the service from &lt;code class="language-plaintext highlighter-rouge"&gt;RunTimeServiceLocator&lt;/code&gt;, a process-wide registry of service factories that PostSharp 2027.0 adds. Register the services there at startup, from your old composition code or from the new container:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;RunTimeServiceLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAuditLog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConsoleAuditLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;RunTimeServiceLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConsoleNotifier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderRepository&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A required service that nobody registered fails at construction with a message that says what to do:&lt;/p&gt;

&lt;div class="language-text highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;No service of type MyApp.IAuditLog is registered in RunTimeServiceLocator. Register it with RunTimeServiceLocator.RegisterService, or pass it to the constructor.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;An optional service that nobody registered stays &lt;code class="language-plaintext highlighter-rouge"&gt;null&lt;/code&gt;, which is why the advice above writes &lt;code class="language-plaintext highlighter-rouge"&gt;metrics?.Increment&lt;/code&gt;. A lazy service is resolved, and fails if needed, when the advice first uses it.&lt;/p&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;RunTimeServiceLocator&lt;/code&gt; is a bridge for the objects created with &lt;code class="language-plaintext highlighter-rouge"&gt;new&lt;/code&gt;, and it is deliberately limited: it has no scopes and does not dispose anything. As more of the application moves to the container, fewer objects need it.&lt;/p&gt;

&lt;h2 id="caching-receives-its-cache-from-the-container"&gt;Caching receives its cache from the container&lt;/h2&gt;

&lt;p&gt;PostSharp Caching is the first library of ours that uses this mechanism. Set &lt;code class="language-plaintext highlighter-rouge"&gt;UseDependencyInjection&lt;/code&gt; on &lt;code class="language-plaintext highlighter-rouge"&gt;[CacheConfiguration]&lt;/code&gt;, on the assembly or on a class, and the classes with cached methods receive an &lt;code class="language-plaintext highlighter-rouge"&gt;ICachingService&lt;/code&gt; from their constructors:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CacheConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;UseDependencyInjection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCatalog&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProductCatalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IgnoreThisParameter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;GetProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;InvalidateCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetProduct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;RenameProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The cache, its backend and its profiles are now configured in the composition root, next to the services they depend on, such as the &lt;code class="language-plaintext highlighter-rouge"&gt;IMemoryCache&lt;/code&gt; of the container:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMemoryCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CachingBackend&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryCacheBackend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMemoryCache&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICachingService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cachingService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CachingService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CachingBackend&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cachingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbsoluteExpiration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cachingService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductCatalog&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each container now has its own cache, disposed with the container, so two tests in the same process no longer see each other’s cached items. A test can also replace &lt;code class="language-plaintext highlighter-rouge"&gt;ICachingService&lt;/code&gt; with its own implementation.&lt;/p&gt;

&lt;p&gt;Static methods, and methods of structs, have no constructor to receive the service. They keep using &lt;code class="language-plaintext highlighter-rouge"&gt;CachingServices.DefaultService&lt;/code&gt;, the process-wide service that replaces &lt;code class="language-plaintext highlighter-rouge"&gt;DefaultBackend&lt;/code&gt;, and PostSharp reports the warning CAC011 so that you know which ones. Raise it to an error if every cached method must use the container.&lt;/p&gt;

&lt;h2 id="try-it"&gt;Try it&lt;/h2&gt;

&lt;p&gt;Dependency injection is available from PostSharp 2027.0.6-preview. The documentation covers both halves of the feature:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://doc.postsharp.net/postsharp/preview/framework/aspects/dependency-injection/constructor-dependencies"&gt;Receiving Dependencies from the Constructors of the Target Class&lt;/a&gt; for your own aspects.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://doc.postsharp.net/postsharp/preview/caching/dependency-injection"&gt;Using Caching with Dependency Injection&lt;/a&gt; for PostSharp Caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See &lt;a href="https://doc.postsharp.net/postsharp/preview/introduction/whats-new/2027-0"&gt;What’s New in PostSharp 2027.0&lt;/a&gt; for the rest of the release. Pick the aspect that holds your oldest service locator, convert it, and tell us how it went on &lt;a href="https://github.com/postsharp/PostSharp.Public/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy meta-programming with PostSharp!&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/postsharp-dependency-injection"&gt;Moving PostSharp Aspects to Dependency Injection&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Tue, 27 Oct 2026 07:00:00 +0000</pubDate>
        
        <link>https://postsharp.net/blog/postsharp-dependency-injection</link>
        <guid isPermaLink="true">https://postsharp.net/blog/postsharp-dependency-injection</guid>
        
        
        <category>PostSharp</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-10-dependency-injection/postsharp.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>Your Aspects, Faster: Advice Parameter Binding in PostSharp 2027.0</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-10-advice-parameter-binding/postsharp.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;Every &lt;code class="language-plaintext highlighter-rouge"&gt;OnEntry( MethodExecutionArgs args )&lt;/code&gt; you have ever written pays three costs on every call: it allocates an object on the heap, it boxes the arguments and the return value into &lt;code class="language-plaintext highlighter-rouge"&gt;object&lt;/code&gt;, and it makes you cast them back. Our own Pattern Libraries stopped paying those costs a long time ago, because they use a mechanism we kept internal: &lt;em&gt;advice parameter binding&lt;/em&gt;. With PostSharp 2027.0, this mechanism is public, documented and checked at build time.&lt;/p&gt;

&lt;h2 id="what-methodexecutionargs-costs-you"&gt;What MethodExecutionArgs costs you&lt;/h2&gt;

&lt;p&gt;Here is a typical logging aspect written as a simple aspect. It logs the first argument, the return value, the exception and the duration of the method:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSerializable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LogAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OnMethodBoundaryAspect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MethodExecutionArgs&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"Entering {0}({1})."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodExecutionTag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MethodExecutionArgs&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"{0} returned {1}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReturnValue&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MethodExecutionArgs&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"{0} failed: {1}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;MethodExecutionArgs&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodExecutionTag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"{0} took {1} ms."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It works, and it has worked for many years. But look at what it asks of the runtime:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;One allocation per call.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;MethodExecutionArgs&lt;/code&gt; lives on the heap, and so does the &lt;code class="language-plaintext highlighter-rouge"&gt;Arguments&lt;/code&gt; collection.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Boxing.&lt;/strong&gt; The &lt;code class="language-plaintext highlighter-rouge"&gt;Arguments&lt;/code&gt; collection stores the arguments with their own types, but exposes them as &lt;code class="language-plaintext highlighter-rouge"&gt;object&lt;/code&gt;, so reading an &lt;code class="language-plaintext highlighter-rouge"&gt;int&lt;/code&gt; through &lt;code class="language-plaintext highlighter-rouge"&gt;args.Arguments[0]&lt;/code&gt; boxes it. &lt;code class="language-plaintext highlighter-rouge"&gt;args.ReturnValue&lt;/code&gt; is an &lt;code class="language-plaintext highlighter-rouge"&gt;object&lt;/code&gt; too, so an &lt;code class="language-plaintext highlighter-rouge"&gt;int&lt;/code&gt; return value is boxed as well.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Casts.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;MethodExecutionTag&lt;/code&gt; is an &lt;code class="language-plaintext highlighter-rouge"&gt;object&lt;/code&gt; too, so the stopwatch must be cast back, and a wrong cast fails at run time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a method called a few times per request, nobody notices. On a hot path, the aspect becomes the most expensive part of the method.&lt;/p&gt;

&lt;h2 id="the-same-aspect-with-bound-parameters"&gt;The same aspect with bound parameters&lt;/h2&gt;

&lt;p&gt;With advice parameter binding, the aspect derives from &lt;code class="language-plaintext highlighter-rouge"&gt;MethodLevelAspect&lt;/code&gt;, and each advice method declares only the values it needs, as typed parameters. A &lt;em&gt;binding attribute&lt;/em&gt; on each parameter says where the value comes from:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSerializable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LogAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MethodLevelAspect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OnMethodEntryAdvice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DeclarationName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;StateScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodInvocation&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"Entering {0}({1})."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OnMethodSuccessAdvice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;OnSuccess&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DeclarationName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ReturnValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;returnValue&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"{0} returned {1}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;returnValue&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OnMethodExceptionAdvice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DeclarationName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"{0} failed: {1}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OnMethodExitAdvice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DeclarationName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;StateScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodInvocation&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"{0} took {1} ms."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The bindings in this example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;[DeclarationName]&lt;/code&gt;&lt;/strong&gt; gives the name of the method. It is a constant, computed at build time, so there is no reflection call at run time.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;[State]&lt;/code&gt;&lt;/strong&gt; declares a typed variable shared by the advices for one execution of the method. The entry advice assigns it with &lt;code class="language-plaintext highlighter-rouge"&gt;out&lt;/code&gt;, and the exit advice reads it. There is no &lt;code class="language-plaintext highlighter-rouge"&gt;MethodExecutionTag&lt;/code&gt; and no cast.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;[ReturnValue]&lt;/code&gt;&lt;/strong&gt; on a generic parameter &lt;code class="language-plaintext highlighter-rouge"&gt;T&lt;/code&gt; receives the return value without boxing. PostSharp infers &lt;code class="language-plaintext highlighter-rouge"&gt;T&lt;/code&gt; for each target method.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;[Argument( 0 )]&lt;/code&gt;&lt;/strong&gt; reads the first argument. Here it is still boxed, because the parameter is &lt;code class="language-plaintext highlighter-rouge"&gt;object&lt;/code&gt;. Make the advice generic to avoid it, as shown below.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;[Exception]&lt;/code&gt;&lt;/strong&gt; gives the exception, typed as &lt;code class="language-plaintext highlighter-rouge"&gt;Exception&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The aspect is applied exactly as before, with &lt;code class="language-plaintext highlighter-rouge"&gt;[Log]&lt;/code&gt; on a method or with multicasting.&lt;/p&gt;

&lt;h2 id="what-postsharp-actually-generates"&gt;What PostSharp actually generates&lt;/h2&gt;

&lt;p&gt;Bound parameters are a build-time contract, so the woven code passes the values directly. Take a smaller profiling aspect, with an entry advice that starts a &lt;code class="language-plaintext highlighter-rouge"&gt;[State]&lt;/code&gt; stopwatch and an exit advice that prints the &lt;code class="language-plaintext highlighter-rouge"&gt;[DeclarationName]&lt;/code&gt; and the duration (see &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/aspects/member-level-aspects/advices"&gt;Aspect Development Concepts&lt;/a&gt;). PostSharp transforms the target method into code equivalent to this:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Stopwatch&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Method body.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"ProfileAspect.OrderService.PlaceOrder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The state is a local variable, the method name is a string literal, and the advices are plain method calls. Apart from the &lt;code class="language-plaintext highlighter-rouge"&gt;Stopwatch&lt;/code&gt; itself, the woven code allocates nothing.&lt;/p&gt;

&lt;h2 id="generic-advices-inferred-for-each-target"&gt;Generic advices, inferred for each target&lt;/h2&gt;

&lt;p&gt;An advice method can be generic. PostSharp infers the type arguments separately for each target method, with rules similar to the type inference of the C# compiler, and then calls a different instantiation of the advice from each target.&lt;/p&gt;

&lt;p&gt;Take the &lt;code class="language-plaintext highlighter-rouge"&gt;OnSuccess&amp;lt;T&amp;gt;&lt;/code&gt; advice above. On &lt;code class="language-plaintext highlighter-rouge"&gt;int Square( int value )&lt;/code&gt;, PostSharp calls &lt;code class="language-plaintext highlighter-rouge"&gt;OnSuccess&amp;lt;int&amp;gt;&lt;/code&gt;. On &lt;code class="language-plaintext highlighter-rouge"&gt;string Format( Order order )&lt;/code&gt;, it calls &lt;code class="language-plaintext highlighter-rouge"&gt;OnSuccess&amp;lt;string&amp;gt;&lt;/code&gt;. The same source method serves every signature, and an &lt;code class="language-plaintext highlighter-rouge"&gt;int&lt;/code&gt; stays an &lt;code class="language-plaintext highlighter-rouge"&gt;int&lt;/code&gt; all the way.&lt;/p&gt;

&lt;p&gt;Inference also works through constructed types and constraints:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OnMethodEntryAdvice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;OnEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DeclarationName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComparable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"{0} received {1} items, the first is {2}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Applied to a method that takes a &lt;code class="language-plaintext highlighter-rouge"&gt;List&amp;lt;int&amp;gt;&lt;/code&gt;, this advice gets &lt;code class="language-plaintext highlighter-rouge"&gt;T = int&lt;/code&gt;: PostSharp matches &lt;code class="language-plaintext highlighter-rouge"&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; against the type of the argument, its base types and its interfaces, as C# would. The rules, in short:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;No boxing.&lt;/strong&gt; A value of a value type is passed as it is, and a &lt;code class="language-plaintext highlighter-rouge"&gt;ref&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;out&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;in&lt;/code&gt; parameter receives the location itself.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Common type.&lt;/strong&gt; When several bound values give a type argument, it is the type to which all of them can be assigned. &lt;code class="language-plaintext highlighter-rouge"&gt;OnEntry&amp;lt;T&amp;gt;( [Argument( 0 )] T a, [Argument( 1 )] T b )&lt;/code&gt; on &lt;code class="language-plaintext highlighter-rouge"&gt;M( string, object )&lt;/code&gt; gives &lt;code class="language-plaintext highlighter-rouge"&gt;T = object&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Constructed types.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;T[]&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;T?&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;IList&amp;lt;T&amp;gt;&lt;/code&gt; and the like are matched against the value’s type, its base types and its interfaces.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Constraints are checked.&lt;/strong&gt; Including &lt;code class="language-plaintext highlighter-rouge"&gt;unmanaged&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;new()&lt;/code&gt; and constraints that refer to other type parameters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a type argument cannot be inferred for a target, or violates a constraint, the build fails with the error LA0220, which states the reason. Apply the advice above to a method that takes a &lt;code class="language-plaintext highlighter-rouge"&gt;List&amp;lt;object&amp;gt;&lt;/code&gt;, and you learn at build time that &lt;code class="language-plaintext highlighter-rouge"&gt;object&lt;/code&gt; does not implement &lt;code class="language-plaintext highlighter-rouge"&gt;IComparable&amp;lt;object&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The type of a parameter can also use the type parameters of a generic aspect class. They are replaced by the type arguments of the aspect instance. See &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/aspects/member-level-aspects/advice-parameter-binding#generic-advice-methods"&gt;Generic advice methods&lt;/a&gt; for the complete rules.&lt;/p&gt;

&lt;h2 id="errors-at-build-time-not-in-production"&gt;Errors at build time, not in production&lt;/h2&gt;

&lt;p&gt;A simple aspect accepts any method, then fails at run time if it makes a wrong assumption, for instance by reading &lt;code class="language-plaintext highlighter-rouge"&gt;args.Arguments[0]&lt;/code&gt; on a method that has no parameters.&lt;/p&gt;

&lt;p&gt;With bound parameters, PostSharp checks each advice against each target when it weaves the aspect. An advice that does not fit a target is a build error with a message code. Apply the &lt;code class="language-plaintext highlighter-rouge"&gt;LogAttribute&lt;/code&gt; above to a method without parameters, and &lt;code class="language-plaintext highlighter-rouge"&gt;[Argument( 0 )]&lt;/code&gt; causes the error LA0225: the target method has no parameter at this index. A parameter of the wrong type, a binding used in an advice that does not support it, or a type argument that cannot be inferred all have their own codes. PostSharp reports all the errors of an aspect in one build.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/aspects/member-level-aspects/advice-parameter-binding#build-messages"&gt;complete list of build messages&lt;/a&gt; is in the reference.&lt;/p&gt;

&lt;h2 id="beyond-the-basics"&gt;Beyond the basics&lt;/h2&gt;

&lt;p&gt;The logging example uses five bindings. The &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/aspects/member-level-aspects/advice-parameter-binding"&gt;Advice Parameter Reference&lt;/a&gt; lists them all. Among them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Write access with &lt;code class="language-plaintext highlighter-rouge"&gt;ref&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;out&lt;/code&gt;.&lt;/strong&gt; Replace an argument, set or replace the return value, or replace the exception.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;[FlowBehavior]&lt;/code&gt;&lt;/strong&gt; to return without executing the method, or to swallow an exception.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Async methods.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;[CurrentTask]&lt;/code&gt; gives the task that the caller observes, including for &lt;code class="language-plaintext highlighter-rouge"&gt;ValueTask&lt;/code&gt; methods, and &lt;code class="language-plaintext highlighter-rouge"&gt;[AsyncCallId]&lt;/code&gt; identifies one execution of the async method.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Interception.&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;[Binding]&lt;/code&gt; gives an object that invokes the original method, field or property.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;[This]&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;[Declaration]&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;[DeclarationIdentifier]&lt;/code&gt;&lt;/strong&gt; for the instance and the reflection object of the target.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grouping also got simpler. &lt;code class="language-plaintext highlighter-rouge"&gt;[SelfPointcut]&lt;/code&gt; and the &lt;code class="language-plaintext highlighter-rouge"&gt;Master&lt;/code&gt; property are optional when the grouping of the advices is unambiguous, so an aspect that applies to a single method, field, property or event needs neither.&lt;/p&gt;

&lt;h2 id="should-you-modernize-your-aspects"&gt;Should you modernize your aspects?&lt;/h2&gt;

&lt;p&gt;Not necessarily. Simple aspects remain fully supported, and you can keep the &lt;code class="language-plaintext highlighter-rouge"&gt;AdviceArgs&lt;/code&gt; signature in the following cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The aspect intercepts events, validates fields or parameters, or uses &lt;code class="language-plaintext highlighter-rouge"&gt;OnInvokeAsync&lt;/code&gt;. These advices do not accept bound parameters yet.&lt;/li&gt;
  &lt;li&gt;The aspect reads the value yielded by an iterator, which only &lt;code class="language-plaintext highlighter-rouge"&gt;MethodExecutionArgs.YieldValue&lt;/code&gt; gives.&lt;/li&gt;
  &lt;li&gt;The aspect is not on a performance-sensitive path, and you prefer the simpler programming model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/aspects/member-level-aspects/advice-parameter-binding#limitations"&gt;limitations&lt;/a&gt; are listed in the reference.&lt;/p&gt;

&lt;p&gt;For the aspects that do run on hot paths, the conversion is mechanical. Change the base class to &lt;code class="language-plaintext highlighter-rouge"&gt;MethodLevelAspect&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;LocationLevelAspect&lt;/code&gt;, turn each override into an advice method with the matching attribute, replace each &lt;code class="language-plaintext highlighter-rouge"&gt;args&lt;/code&gt; member with a bound parameter, and build. PostSharp tells you what does not fit. The &lt;a href="https://doc.postsharp.net/postsharp/preview/framework/aspects/member-level-aspects/modernizing-aspects"&gt;Modernizing Your Custom Aspects&lt;/a&gt; article gives the steps and a table that maps each member of &lt;code class="language-plaintext highlighter-rouge"&gt;MethodExecutionArgs&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;MethodInterceptionArgs&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;LocationInterceptionArgs&lt;/code&gt; to its binding.&lt;/p&gt;

&lt;h2 id="try-it"&gt;Try it&lt;/h2&gt;

&lt;p&gt;Advice parameter binding is available from PostSharp 2027.0, which also adds support for .NET 11 and C# 15. See &lt;a href="https://doc.postsharp.net/postsharp/preview/introduction/whats-new/2027-0"&gt;What’s New in PostSharp 2027.0&lt;/a&gt; for the full list. Pick one aspect on your hottest path, convert it, and tell us how it went on &lt;a href="https://github.com/postsharp/PostSharp.Public/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy meta-programming with PostSharp!&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/postsharp-advice-parameter-binding"&gt;Your Aspects, Faster: Advice Parameter Binding in PostSharp 2027.0&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Fri, 23 Oct 2026 07:00:00 +0000</pubDate>
        
        <link>https://postsharp.net/blog/postsharp-advice-parameter-binding</link>
        <guid isPermaLink="true">https://postsharp.net/blog/postsharp-advice-parameter-binding</guid>
        
        
        <category>PostSharp</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-10-advice-parameter-binding/postsharp.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>Security and Privacy Update for PostSharp and Metalama</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-06-security/metalama.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;Today we are shipping a coordinated security and privacy update across every supported branch of PostSharp and Metalama. It covers the build toolchain, the design time services, telemetry, and the Visual Studio Tools. It does not change the public API or how you write aspects, and none of it reaches your compiled application: every issue is in tooling that runs on developer and build machines, so the applications you have already built and shipped are not affected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; All fixes are already shipped on every supported branch. The most serious issues (three High-severity advisories) require an attacker to already have a local account on your machine, so they mainly affect shared or multi-user build agents. Upgrade when convenient; prioritize it if you build on shared agents. Nothing else to do.&lt;/p&gt;

&lt;h2 id="scope-and-attack-vectors"&gt;Scope and attack vectors&lt;/h2&gt;

&lt;p&gt;We published GitHub Security Advisories for the most severe findings, all rated High. They require the attacker to already have local access to the same machine, under a different user account, which mainly matters on shared or multi-user build agents.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;PostSharp&lt;/strong&gt;:
    &lt;ul&gt;
      &lt;li&gt;Local code execution, information disclosure, and denial of service in the build toolchain on shared machines (&lt;a href="https://github.com/postsharp/PostSharp.Public/security/advisories/GHSA-gpwg-4q4j-792q"&gt;GHSA-gpwg-4q4j-792q&lt;/a&gt;)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Metalama&lt;/strong&gt;:
    &lt;ul&gt;
      &lt;li&gt;Unsafe deserialization over the Visual Studio named pipe enables local code execution (&lt;a href="https://github.com/metalama/Metalama/security/advisories/GHSA-h26j-4vp7-x9w2"&gt;GHSA-h26j-4vp7-x9w2&lt;/a&gt;)&lt;/li&gt;
      &lt;li&gt;Local code execution via DLL planting in world-writable temp directories on Linux/macOS (&lt;a href="https://github.com/metalama/Metalama/security/advisories/GHSA-m6wq-39m7-9xpp"&gt;GHSA-m6wq-39m7-9xpp&lt;/a&gt;)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We found and fixed other vulnerabilities that required the attacker to sit between your device and our &lt;code class="language-plaintext highlighter-rouge"&gt;postsharp.net&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;metalama.net&lt;/code&gt; servers as a man-in-the-middle. See the GitHub release notes in the &lt;em&gt;What changed&lt;/em&gt; section below.&lt;/p&gt;

&lt;p&gt;None of these vulnerabilities could be exploited by a remote attacker against a normal developer or CI setup.&lt;/p&gt;

&lt;p&gt;One assumption is unchanged: like the .NET SDK itself, PostSharp and Metalama assume your source code and NuGet dependencies are trusted, and they execute that code at build time. This review hardened everything around that boundary, but it does not sandbox hostile source or malicious packages. Vetting what enters your build remains your job.&lt;/p&gt;

&lt;p&gt;There is no known exploitation, and no customer needs to do anything beyond upgrading when convenient.&lt;/p&gt;

&lt;h2 id="why-now-and-not-sooner"&gt;Why now, and not sooner&lt;/h2&gt;

&lt;p&gt;No attacker or customer reported these issues. We found and disclosed them ourselves, and published the advisories with the fixes already shipped on every supported branch.&lt;/p&gt;

&lt;p&gt;Why now? Because the new generation of AI models makes it both possible and urgent. Auditing a 20-year-old living codebase used to mean weeks of work and skills we didn’t have in-house. It did not seem necessary for a niche tool that processes trusted local inputs without network access. However, with new AI tools, an in-depth security analysis takes a couple of hours, both for the publisher and the attacker. We used Claude Opus 4.8 for the review, reviewed every finding, supervised and refined the fixes, and tested them on real VMs before release. The whole pass still took weeks; without the model it would not have happened.&lt;/p&gt;

&lt;h2 id="run-the-analysis-yourself"&gt;Run the analysis yourself&lt;/h2&gt;

&lt;p&gt;We believe that security stems from transparency. Ultimately, it does not matter &lt;em&gt;who&lt;/em&gt; wrote the code you execute. What matters is the code itself.&lt;/p&gt;

&lt;p&gt;Metalama is open source: clone it from &lt;a href="https://github.com/metalama/Metalama"&gt;GitHub&lt;/a&gt; and point your own tools at it. PostSharp’s source is available to enterprise customers at no additional charge under a source-available license, going back to version 1.0; see &lt;a href="/blog/postsharp-source-available"&gt;PostSharp Is Now Source Available&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you run your own analysis, report any defects privately through GitHub Security Advisories (&lt;a href="https://github.com/metalama/Metalama/security/advisories"&gt;Metalama&lt;/a&gt;, &lt;a href="https://github.com/postsharp/PostSharp.Public/security/advisories"&gt;PostSharp&lt;/a&gt;) and we will handle them the way we handled this review. Our &lt;a href="https://www.postsharp.net/compliance/security"&gt;security policy&lt;/a&gt; describes how we receive, prioritize, and disclose vulnerabilities.&lt;/p&gt;

&lt;h2 id="what-changed"&gt;What changed&lt;/h2&gt;

&lt;p&gt;Across all these releases, the work falls into a few themes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Safer behavior on shared machines.&lt;/strong&gt; Caches, temp files, and local servers now stay scoped to the current user, so one user cannot interfere with or read another’s build.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Telemetry hardened to minimize what leaves your machine.&lt;/strong&gt; Crash reports now omit exception messages and data, and do a more thorough job scrubbing both secrets and confidential identifiers, such as a company name embedded in a namespace, so a stack trace can’t carry a token, a connection string, or your internal names off your machine. We rotate device IDs monthly and keep separate salts for our own diagnostics and our third-party analytics, so the two are much harder to correlate. For exactly what we collect and how to turn it off, see our &lt;a href="https://www.postsharp.net/company/legal/privacy-policy"&gt;privacy policy&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Removed features that collected data.&lt;/strong&gt; We took out the in-product extras that came with a data cost: the Learning Hub tool window in Visual Studio, the Areas of Interest selection, and newsletter and email registration. We also removed the telemetry code for the long-defunct per-usage licensing model, which used to upload reversibly-hashed type names.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Defense in depth against our own servers.&lt;/strong&gt; Some data comes from postsharp.net or metalama.net, such as the news feed and content feeds. We hardened the parsers and serializers that handle it, so a compromise of our servers could not turn that data into code execution on your machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LTS and latest branches get the full set of changes. The two Metalama advisory fixes were also backported to the other supported versions, 2025.1.18 and 2026.0.25, without the broader privacy and telemetry rework. See the &lt;a href="https://www.postsharp.net/support/policies/versions"&gt;version status page&lt;/a&gt; for what is still serviced.&lt;/p&gt;

&lt;p&gt;The per-release notes have the full technical detail:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;PostSharp&lt;/strong&gt;:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="https://github.com/postsharp/PostSharp.Public/releases/tag/v2026.0.13"&gt;2026.0.13&lt;/a&gt; (Current)&lt;/li&gt;
      &lt;li&gt;&lt;a href="https://github.com/postsharp/PostSharp.Public/releases/tag/v2024.0.23"&gt;2024.0.23&lt;/a&gt; (LTS)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Metalama&lt;/strong&gt;:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release/2026.1.19"&gt;2026.1.19&lt;/a&gt; (Current)&lt;/li&gt;
      &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release/2026.0.25"&gt;2026.0.25&lt;/a&gt; (Extended Support: advisory fixes only)&lt;/li&gt;
      &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release/2025.1.18"&gt;2025.1.18&lt;/a&gt; (Extended Support: advisory fixes only)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Visual Studio Tools for PostSharp and Metalama&lt;/strong&gt;:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama.Vsx.Public/releases/tag/release/2026.1.5"&gt;2026.1.5&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All packages are on &lt;a href="https://www.nuget.org/packages/Metalama.Framework"&gt;NuGet.org&lt;/a&gt;, and the extension is on the &lt;a href="https://marketplace.visualstudio.com/items?itemName=PostSharpTechnologies.PostSharp"&gt;Visual Studio Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="updated-policies"&gt;Updated policies&lt;/h2&gt;

&lt;p&gt;As part of this review we updated our &lt;a href="https://www.postsharp.net/company/legal/privacy-policy"&gt;privacy policy&lt;/a&gt; to describe exactly what we collect, and our &lt;a href="https://www.postsharp.net/compliance/security"&gt;security policy&lt;/a&gt; to reflect the current threat model and how to report a vulnerability.&lt;/p&gt;

&lt;h2 id="how-to-upgrade"&gt;How to upgrade&lt;/h2&gt;

&lt;p&gt;For most users this is a drop-in upgrade: bump the NuGet versions and update the extension. No API changes, nothing to reconfigure. Open-source and free-edition users can upgrade freely; for commercial editions, upgrading requires an active maintenance subscription. If you build on shared or multi-user agents, prioritize it.&lt;/p&gt;

&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;

&lt;p&gt;The next Metalama and PostSharp feature releases will focus on .NET 11 support, with work starting in late August.&lt;/p&gt;

&lt;p&gt;Found an issue or have a feature request? Let us know on &lt;a href="https://github.com/metalama/Metalama/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy meta-programming!&lt;/p&gt;

&lt;p&gt;-gael&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/security-privacy-hardening-2026-06"&gt;Security and Privacy Update for PostSharp and Metalama&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Tue, 30 Jun 2026 19:00:01 +0000</pubDate>
        
        <link>https://postsharp.net/blog/security-privacy-hardening-2026-06</link>
        <guid isPermaLink="true">https://postsharp.net/blog/security-privacy-hardening-2026-06</guid>
        
        <category>featured</category>
        
        
        <category>PostSharp</category>
        
        <category>Metalama</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-06-security/metalama.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>Metalama 2026.1 Generally Available: First LTS Release, Complete C# 14 Support, and a Redesigned Redis Backend</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-06-announcement/metalama.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;Metalama 2026.1 is now generally available, and it’s our first &lt;strong&gt;long-term support (LTS)&lt;/strong&gt; release. This is a consolidation release: we finished the C# 14 work we started in 2026.0, extended the advising API, redesigned the Redis caching backend, cut several third-party dependencies, and closed most of the bug backlog.&lt;/p&gt;

&lt;p&gt;The packages are on &lt;a href="https://www.nuget.org/packages/Metalama.Framework"&gt;NuGet.org&lt;/a&gt;, and the Visual Studio extension is on the &lt;a href="https://marketplace.visualstudio.com/items?itemName=PostSharpTechnologies.PostSharp"&gt;Visual Studio Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the complete list of changes, see the &lt;a href="https://doc.metalama.net/conceptual/release-notes/release-notes-2026-1"&gt;release notes&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="complete-c-14-support-in-aspects"&gt;Complete C# 14 support in aspects&lt;/h2&gt;

&lt;p&gt;In 2026.0 we supported all C# 14 features in your own code but left a few gaps in aspects. Those gaps are now closed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Introducing extension blocks.&lt;/strong&gt; The new &lt;a href="https://doc.metalama.net/api/metalama-framework-aspects-adviserextensions-introduceextensionblock"&gt;IntroduceExtensionBlock&lt;/a&gt; advice lets aspects add extension blocks to static classes. See &lt;a href="https://doc.metalama.net/conceptual/aspects/advising/introducing-types"&gt;Introducing types&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Contracts on extension-block receiver parameters.&lt;/strong&gt; You can now add a contract to the receiver parameter of an extension block, and it propagates across the extension members.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Introducing user-defined compound assignment operators.&lt;/strong&gt; Operators such as &lt;code class="language-plaintext highlighter-rouge"&gt;+=&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;-=&lt;/code&gt;, and &lt;code class="language-plaintext highlighter-rouge"&gt;*=&lt;/code&gt; can now be introduced through &lt;a href="https://doc.metalama.net/conceptual/aspects/advising/introducing-members"&gt;IntroduceMethod&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Run-time invoker interfaces for extension members.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Introducing parameters into partial constructors.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two C# 14 constructs remain unsupported &lt;em&gt;inside templates&lt;/em&gt;: null-conditional assignments (&lt;code class="language-plaintext highlighter-rouge"&gt;x?.P = v&lt;/code&gt;) and the &lt;code class="language-plaintext highlighter-rouge"&gt;field&lt;/code&gt; keyword. Rather than fail confusingly, Metalama now detects them and reports a clear diagnostic (&lt;code class="language-plaintext highlighter-rouge"&gt;LAMA0101&lt;/code&gt;).&lt;/p&gt;

&lt;h2 id="initialization-advice"&gt;Initialization advice&lt;/h2&gt;

&lt;p&gt;We’ve extended the &lt;a href="https://doc.metalama.net/conceptual/aspects/advising/initializers"&gt;initialization advice&lt;/a&gt; with two new initializer kinds and better support for records:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;AfterLastInstanceConstructor&lt;/code&gt;&lt;/strong&gt; injects code into a &lt;code class="language-plaintext highlighter-rouge"&gt;protected virtual OnConstructed&lt;/code&gt; method, so it runs once after the most-derived constructor completes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;AfterObjectInitializer&lt;/code&gt;&lt;/strong&gt; runs after the constructor and any object initializer or &lt;code class="language-plaintext highlighter-rouge"&gt;with&lt;/code&gt; expression have completed. Metalama makes the target implement &lt;code class="language-plaintext highlighter-rouge"&gt;IInitializable&lt;/code&gt; and rewrites the call sites accordingly, so you can validate or compute derived state once every property has been set.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;BeforeInstanceConstructor&lt;/code&gt;&lt;/strong&gt; now works on positional records with primary constructors.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href="https://doc.metalama.net/conceptual/aspects/advising/introducing-constructor-parameters"&gt;Constructor parameter introduction&lt;/a&gt;&lt;/strong&gt; can now generate forwarding overloads, so you can add a parameter without breaking binary compatibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="dependency-injection"&gt;Dependency injection&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://doc.metalama.net/api/metalama-framework-advising-pullstrategy-introduceparameterandpull"&gt;IntroduceParameterAndPull&lt;/a&gt; now accepts a &lt;code class="language-plaintext highlighter-rouge"&gt;reuseExistingParameterOfCompatibleType&lt;/code&gt; argument. When a chained constructor already accepts a parameter of the same or a more specific type, the existing parameter is forwarded instead of duplicated. The default .NET DI adapter relies on this: an aspect that pulls an &lt;code class="language-plaintext highlighter-rouge"&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt; no longer adds a second &lt;code class="language-plaintext highlighter-rouge"&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt; parameter when the target constructor already has one. See &lt;a href="https://doc.metalama.net/conceptual/aspects/dependency-injection"&gt;Injecting dependencies into aspects&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="redesigned-redis-caching-backend"&gt;Redesigned Redis caching backend&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://doc.metalama.net/patterns/caching/redis"&gt;Metalama.Patterns.Caching.Backends.Redis&lt;/a&gt; received a major overhaul, porting the work we did for PostSharp 2025.1 (see &lt;a href="/blog/redis-caching-update"&gt;Breaking Update of PostSharp Caching adapter for Redis in 2025.1&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;New data schema.&lt;/strong&gt; Reading a cache item is now a single, observationally-consistent operation, and cache keys are versioned.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cluster support.&lt;/strong&gt; Hash tags keep related keys on the same shard, and you can configure &lt;code class="language-plaintext highlighter-rouge"&gt;ReadCommandFlags&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;WriteCommandFlags&lt;/code&gt; for multi-node setups.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Retry and exception-handling policies.&lt;/strong&gt; The new &lt;code class="language-plaintext highlighter-rouge"&gt;IRetryPolicy&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;IExceptionHandlingPolicy&lt;/code&gt; interfaces give you exponential backoff and centralized error handling.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Key compression.&lt;/strong&gt; Keys longer than a configurable threshold (128 characters by default) are hashed automatically with &lt;code class="language-plaintext highlighter-rouge"&gt;XxHash64&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;XxHash128&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Overload detection.&lt;/strong&gt; The &lt;code class="language-plaintext highlighter-rouge"&gt;IsBackgroundTaskQueueOverloaded&lt;/code&gt; property lets you back off under pressure, and the dependency garbage collector pauses itself when the queue is saturated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use the Redis backend, note that the schema change requires purging the cache (&lt;code class="language-plaintext highlighter-rouge"&gt;FLUSHDB&lt;/code&gt;) on upgrade. See the breaking changes below.&lt;/p&gt;

&lt;h2 id="fewer-third-party-dependencies"&gt;Fewer third-party dependencies&lt;/h2&gt;

&lt;p&gt;We reduced our dependency surface to improve supply-chain safety:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;System.Text.Json&lt;/code&gt; replaces &lt;code class="language-plaintext highlighter-rouge"&gt;Newtonsoft.Json&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;System.IO.Hashing&lt;/code&gt; replaces &lt;code class="language-plaintext highlighter-rouge"&gt;K4os.Hash&lt;/code&gt; (the xxHash implementation now comes from the .NET runtime).&lt;/li&gt;
  &lt;li&gt;Diff-tool integration moved to the optional &lt;code class="language-plaintext highlighter-rouge"&gt;Metalama.Extensions.DiffEngine&lt;/code&gt; package, and HTML test output moved to &lt;code class="language-plaintext highlighter-rouge"&gt;Metalama.Extensions.HtmlWriter&lt;/code&gt; (both used in &lt;a href="https://doc.metalama.net/conceptual/aspects/testing/snapshot-testing"&gt;aspect snapshot testing&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;We no longer use MD5 for non-cryptographic hashing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="performance-improvements"&gt;Performance improvements&lt;/h2&gt;

&lt;p&gt;This release improves both build-time and design-time performance. At build time, configuration files now use &lt;code class="language-plaintext highlighter-rouge"&gt;System.Text.Json&lt;/code&gt; source generators, polymorphic pattern matching in the code model was restructured to match on &lt;code class="language-plaintext highlighter-rouge"&gt;DeclarationKind&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;TypeKind&lt;/code&gt;, and the WPF temporary compilation phase (&lt;code class="language-plaintext highlighter-rouge"&gt;MarkupCompilePass1&lt;/code&gt;) runs a reduced pipeline that emits member signatures only and skips the linker step. At design time, the protocol between the analyzer and the design-time services moved from JSON to MessagePack.&lt;/p&gt;

&lt;h2 id="bug-fixes"&gt;Bug fixes&lt;/h2&gt;

&lt;p&gt;We fixed over 100 bugs in this release, spanning the template engine, linker, code model, design-time services, dependency injection, and diagnostics. Metalama 2026.1 is the first release ever to ship with (almost) zero known bugs.&lt;/p&gt;

&lt;h2 id="breaking-changes"&gt;Breaking changes&lt;/h2&gt;

&lt;p&gt;A few changes may affect existing code:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;AddInitializer&lt;/code&gt; ordering now respects &lt;code class="language-plaintext highlighter-rouge"&gt;AspectOrderDirection.RunTime&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;IntroduceParameter&lt;/code&gt; on a record primary constructor no longer materializes the parameter by default; opt in with &lt;code class="language-plaintext highlighter-rouge"&gt;materializeOnRecord&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;IConstructor.InitializerKind&lt;/code&gt; returns &lt;code class="language-plaintext highlighter-rouge"&gt;Base&lt;/code&gt; (instead of &lt;code class="language-plaintext highlighter-rouge"&gt;None&lt;/code&gt;) for an implicit &lt;code class="language-plaintext highlighter-rouge"&gt;base()&lt;/code&gt; call.&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;IEvent.RaiseMethod&lt;/code&gt; returns &lt;code class="language-plaintext highlighter-rouge"&gt;null&lt;/code&gt; for non-raisable events. Use &lt;code class="language-plaintext highlighter-rouge"&gt;IEventInvoker.CanRaise&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;The test base classes &lt;code class="language-plaintext highlighter-rouge"&gt;AspectTestClass&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;DefaultAspectTestClass&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;CurrentDirectoryAttribute&lt;/code&gt;, and &lt;code class="language-plaintext highlighter-rouge"&gt;CurrentProjectAttribute&lt;/code&gt; were removed.&lt;/li&gt;
  &lt;li&gt;For the Redis backend: the schema redesign requires a cache purge, &lt;code class="language-plaintext highlighter-rouge"&gt;ExceptionHandlingCachingBackendEnhancer&lt;/code&gt; was removed in favor of the &lt;code class="language-plaintext highlighter-rouge"&gt;ExceptionHandlingPolicy&lt;/code&gt; property, and &lt;code class="language-plaintext highlighter-rouge"&gt;TransactionMaxRetries&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;CacheCleanupOptions.Sequential&lt;/code&gt; are obsolete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href="https://doc.metalama.net/conceptual/release-notes/release-notes-2026-1#breaking-changes"&gt;release notes&lt;/a&gt; for the full list.&lt;/p&gt;

&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;

&lt;p&gt;As our first LTS release, 2026.1 is the version to standardize on if you want stability. The next feature release is expected to be 2027.0, with support for .NET 11. Work will start in late August, and we don’t expect any larger work on Metalama in the meantime.&lt;/p&gt;

&lt;p&gt;Found an issue or have a feature request? Let us know on &lt;a href="https://github.com/metalama/Metalama/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy meta-programming!&lt;/p&gt;

&lt;p&gt;-gael&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/metalama-2026-1-ga"&gt;Metalama 2026.1 Generally Available: First LTS Release, Complete C# 14 Support, and a Redesigned Redis Backend&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Wed, 03 Jun 2026 07:00:01 +0000</pubDate>
        
        <link>https://postsharp.net/blog/metalama-2026-1-ga</link>
        <guid isPermaLink="true">https://postsharp.net/blog/metalama-2026-1-ga</guid>
        
        <category>featured</category>
        
        
        <category>Metalama</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-06-announcement/metalama.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>PostSharp 2026.0 Generally Available: Support for .NET 10, C# 14 and Extension Blocks</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-01-announcement/postsharp.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;Alongside &lt;a href="/blog/metalama-2026-0-ga"&gt;Metalama 2026.0&lt;/a&gt;, we’re announcing the general availability of PostSharp 2026.0. This release brings full support for .NET 10 and C# 14, including proper handling of the new extension blocks syntax introduced in C# 14.&lt;/p&gt;

&lt;h2 id="net-10-and-c-14"&gt;.NET 10 and C# 14&lt;/h2&gt;

&lt;p&gt;We’ve updated and tested PostSharp to fully support the .NET 10 SDK and C# 14 across all packages. You can now confidently use PostSharp with the latest .NET stack.&lt;/p&gt;

&lt;h2 id="extension-blocks-support"&gt;Extension Blocks Support&lt;/h2&gt;

&lt;p&gt;C# 14 introduces extension blocks as an alternative syntax for defining extension methods. This new language feature required careful consideration in PostSharp to ensure predictable behavior.&lt;/p&gt;

&lt;p&gt;By default, multicast attributes now skip extension block members. This prevents aspects from being unintentionally applied to compiler-generated metadata associated with extension blocks.&lt;/p&gt;

&lt;p&gt;For scenarios where you do want to target extension block members, we’ve added the &lt;code class="language-plaintext highlighter-rouge"&gt;AllowExtensionBlockMembers&lt;/code&gt; property to both &lt;code class="language-plaintext highlighter-rouge"&gt;MulticastAttribute&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;MulticastAttributeUsageAttribute&lt;/code&gt;. Setting this property to &lt;code class="language-plaintext highlighter-rouge"&gt;true&lt;/code&gt; enables your aspects to target extension block members explicitly.&lt;/p&gt;

&lt;p&gt;Additionally, developers using &lt;code class="language-plaintext highlighter-rouge"&gt;IAspectProvider&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;IAdviceProvider&lt;/code&gt; interfaces can use the new &lt;code class="language-plaintext highlighter-rouge"&gt;ReflectionHelper.IsExtensionBlockMetadata&lt;/code&gt; utility method to identify compiler-generated metadata associated with extension blocks.&lt;/p&gt;

&lt;h2 id="updated-framework-baselines"&gt;Updated Framework Baselines&lt;/h2&gt;

&lt;p&gt;We’ve raised the minimum supported frameworks to reflect the current .NET ecosystem:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;.NET Standard 2.0&lt;/strong&gt; (minimum)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;.NET Framework 4.7.0&lt;/strong&gt; (minimum)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;.NET 6.0&lt;/strong&gt; (minimum for .NET Core-based projects)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All pre-2017 target frameworks and obsolete .NET Core versions that have lost mainstream support have been removed. Please consult the &lt;a href="https://doc.postsharp.net/introduction/whatsnew/whats-new-20260"&gt;breaking changes documentation&lt;/a&gt; for complete migration guidance.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;PostSharp 2026.0 continues our commitment to keeping PostSharp compatible with the latest .NET releases. While our new feature development is focused on &lt;a href="/blog/metalama-2026-0-ga"&gt;Metalama&lt;/a&gt;, we remain dedicated to ensuring that PostSharp users can stay current with the evolving .NET platform.&lt;/p&gt;

&lt;p&gt;Happy meta-programming with PostSharp!&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/postsharp-2026-0-ga"&gt;PostSharp 2026.0 Generally Available: Support for .NET 10, C# 14 and Extension Blocks&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Wed, 07 Jan 2026 07:00:00 +0000</pubDate>
        
        <link>https://postsharp.net/blog/postsharp-2026-0-ga</link>
        <guid isPermaLink="true">https://postsharp.net/blog/postsharp-2026-0-ga</guid>
        
        
        <category>PostSharp</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-01-announcement/postsharp.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>Metalama 2026.0 Generally Available: Full Support for C# 14 and .NET 10</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2026/2026-01-announcement/metalama.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;Metalama 2026.0 is now generally available! This major release brings full support for C# 14 (the most significant evolution of the C# language in years), along with .NET 10 SDK and Visual Studio 2026 compatibility. We’re also closing one of the last feature gaps with PostSharp: the ability to override event handler invocations. And you’re getting first-class support for tuple types.&lt;/p&gt;

&lt;p&gt;Ready to try it? The packages are on &lt;a href="https://www.nuget.org/packages/Metalama.Framework"&gt;NuGet.org&lt;/a&gt;, and the Visual Studio extension is available from the &lt;a href="https://marketplace.visualstudio.com/items?itemName=PostSharpTechnologies.PostSharp"&gt;Visual Studio Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the complete list of changes, see the &lt;a href="https://doc.metalama.net/conceptual/release-notes/release-notes-2026-0"&gt;release notes&lt;/a&gt;. For even more detail, see the change notes for the releases &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.11"&gt;2026.0.11&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.10-rc"&gt;2026.0.10-rc&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.9-rc"&gt;2026.0.9-rc&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.8-rc"&gt;2026.0.8-rc&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.7-rc"&gt;2026.0.7-rc&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.6-rc"&gt;2026.0.6-rc&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.5-preview"&gt;2026.0.5-preview&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.4-preview"&gt;2026.0.4-preview&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.3-preview"&gt;2026.0.3-preview&lt;/a&gt;, &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.2-preview"&gt;2026.0.2-preview&lt;/a&gt;,
  &lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.1-preview"&gt;2026.0.1-preview&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="c-14-support"&gt;C# 14 support&lt;/h2&gt;

&lt;p&gt;C# 14 introduces several language features, and Metalama 2026.0 supports all of them &lt;em&gt;in user code&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Extension blocks (see below)&lt;/li&gt;
  &lt;li&gt;Null-conditional assignments&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;field&lt;/code&gt; keyword in properties&lt;/li&gt;
  &lt;li&gt;Partial constructors and events&lt;/li&gt;
  &lt;li&gt;Compound assignment operators&lt;/li&gt;
  &lt;li&gt;Simple lambda parameters with modifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, some features are &lt;strong&gt;not yet supported in aspects&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Null-conditional assignments in templates&lt;/li&gt;
  &lt;li&gt;The &lt;code class="language-plaintext highlighter-rouge"&gt;field&lt;/code&gt; keyword in templates&lt;/li&gt;
  &lt;li&gt;Generating run-time code for extension members via invoker interfaces&lt;/li&gt;
  &lt;li&gt;Adding contracts to the receiver parameter of extension blocks&lt;/li&gt;
  &lt;li&gt;Introducing new compound assignment operators&lt;/li&gt;
  &lt;li&gt;Introducing parameters into partial constructors&lt;/li&gt;
  &lt;li&gt;Introducing new extension blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="extension-blocks"&gt;Extension blocks&lt;/h2&gt;

&lt;p&gt;Extension blocks are the headline feature of C# 14, letting you extend any type with new members. Metalama supports them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Extension blocks are modeled via the &lt;code class="language-plaintext highlighter-rouge"&gt;IExtensionBlock&lt;/code&gt; interface, which implements &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType&lt;/code&gt; (&lt;code class="language-plaintext highlighter-rouge"&gt;TypeKind = Extension&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;They appear under &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType.ExtensionBlocks&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;You can override extension members using the advising API or apply aspects via custom attributes.&lt;/li&gt;
  &lt;li&gt;You can introduce new members into existing extension blocks.&lt;/li&gt;
  &lt;li&gt;Introducing &lt;em&gt;new&lt;/em&gt; extension blocks is not yet supported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s how to add logging to all extension methods in your project using a fabric:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fabric&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ProjectFabric&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AmendProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IProjectAmender&lt;/span&gt; &lt;span class="n"&gt;amender&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;amender&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectTypes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExtensionBlocks&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Methods&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAspectIfEligible&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LogAttribute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="event-handler-overriding"&gt;Event handler overriding&lt;/h2&gt;

&lt;p&gt;You can now override event handler invocations, enabling patterns like “safe events” where each handler is isolated by exception handling. The &lt;code class="language-plaintext highlighter-rouge"&gt;OverrideInvoke&lt;/code&gt; method in &lt;code class="language-plaintext highlighter-rouge"&gt;OverrideEventAspect&lt;/code&gt; intercepts individual handler invocations.&lt;/p&gt;

&lt;p&gt;Typical use cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Safe events (exception isolation)&lt;/li&gt;
  &lt;li&gt;Background or asynchronous event dispatch&lt;/li&gt;
  &lt;li&gt;Event invocation logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s an aspect that catches exceptions and removes faulty handlers:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SafeEventAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OverrideEventAspect&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;OverrideInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Proceed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Apply it to any event:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Camera&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SafeEvent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;FocusChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnFocusChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FocusChanged&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For details, see &lt;a href="https://doc.metalama.net/conceptual/aspects/advising/overriding-events"&gt;Overriding events&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="first-class-tuple-support"&gt;First-class tuple support&lt;/h2&gt;

&lt;p&gt;Previously, Metalama treated tuples as plain &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType&lt;/code&gt; objects, with no access to element names or types. The new &lt;code class="language-plaintext highlighter-rouge"&gt;ITupleType&lt;/code&gt; interface changes that, making scenarios like packing method arguments for interceptors much cleaner:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a tuple type from method parameters.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tupleType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TypeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateTupleType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a tuple instance.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tupleExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tupleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCreateInstanceExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Pass it to an observer.&lt;/span&gt;
&lt;span class="n"&gt;_myObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnMethodCalled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeclaringType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tupleExpression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="metrics-enhancements"&gt;Metrics enhancements&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;LinesOfCode metric&lt;/strong&gt;: Three measurements (Logical, NonBlank, and Total).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Workspaces API&lt;/strong&gt;: Now officially supports metrics consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="visual-studio-tooling-improvements"&gt;Visual Studio tooling improvements&lt;/h2&gt;

&lt;p&gt;We’ve refactored several background components of our VSIX. It’s now more stable, consumes less CPU, and better utilizes your cores.&lt;/p&gt;

&lt;h2 id="claude-code-skill-for-ai-assisted-development"&gt;Claude Code skill for AI-assisted development&lt;/h2&gt;

&lt;p&gt;We’re introducing a Claude Code skill that gives Anthropic’s AI assistant Metalama knowledge:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Conceptual documentation&lt;/li&gt;
  &lt;li&gt;API references&lt;/li&gt;
  &lt;li&gt;Sample code and pattern libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you’re new to Metalama or tackling a complex scenario, Claude can now provide contextually relevant guidance for aspect development, templates, fabrics, and the code model.&lt;/p&gt;

&lt;p&gt;The skill is available on the &lt;a href="https://github.com/metalama/Metalama.AI.Skills"&gt;Metalama.AI.Skills marketplace&lt;/a&gt;. For setup instructions, see &lt;a href="https://doc.metalama.net/conceptual/using/claude-code"&gt;Configuring Claude Code&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="documentation-updates"&gt;Documentation updates&lt;/h2&gt;

&lt;p&gt;We’ve made these improvements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;New: &lt;a href="https://doc.metalama.net/conceptual/aspects/type-system"&gt;Working with types&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Improved: &lt;a href="https://doc.metalama.net/conceptual/aspects/templates"&gt;Writing T# templates&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Updated: &lt;a href="https://doc.metalama.net/conceptual/aspects/dependency-injection"&gt;Injecting dependencies into aspects&lt;/a&gt; (now covers cross-project DI)&lt;/li&gt;
  &lt;li&gt;Updated: &lt;a href="https://doc.metalama.net/conceptual/aspects/advising/overriding-events"&gt;Overriding events&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;New: &lt;a href="https://doc.metalama.net/conceptual/introspection/metrics"&gt;Consuming code metrics&lt;/a&gt; and &lt;a href="https://doc.metalama.net/conceptual/sdk/custom-metrics"&gt;Custom metrics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Enhanced API docs with integrated conceptual content&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="breaking-changes"&gt;Breaking changes&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;.NET 6 SDK is no longer supported.&lt;/li&gt;
  &lt;li&gt;Several API and behavioral changes. See the &lt;a href="https://doc.metalama.net/conceptual/release-notes/release-notes-2026-0"&gt;release notes&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="whats-next"&gt;What’s next&lt;/h2&gt;

&lt;p&gt;With 2026.0 shipped, we’ll focus on extending C# 14 support in aspects.&lt;/p&gt;

&lt;p&gt;Found an issue or have a feature request? Let us know on &lt;a href="https://github.com/metalama/Metalama/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy meta-programming!&lt;/p&gt;

&lt;p&gt;-gael&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/metalama-2026-0-ga"&gt;Metalama 2026.0 Generally Available: Full Support for C# 14 and .NET 10&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Mon, 05 Jan 2026 07:00:01 +0000</pubDate>
        
        <link>https://postsharp.net/blog/metalama-2026-0-ga</link>
        <guid isPermaLink="true">https://postsharp.net/blog/metalama-2026-0-ga</guid>
        
        <category>featured</category>
        
        
        <category>Metalama</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2026/2026-01-announcement/metalama.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>Metalama November 2025 Update: Release Candidate for Metalama 2026.0</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2025/2025-11-status/november-dark.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;Metalama 2026.0 has reached Release Candidate status! All planned features are implemented, our test suite is green, and the release is stable enough for production evaluation. We’re now gathering community feedback ahead of our GA release on January 5th, 2026.&lt;/p&gt;

&lt;p&gt;Ready to try it? The RC packages are available on &lt;a href="https://www.nuget.org/packages/Metalama.Framework/2026.0.6-rc"&gt;NuGet.org&lt;/a&gt;, and the Visual Studio extension can be downloaded from the &lt;a href="https://www.postsharp.net/downloads/metalama/metalama-2026.0"&gt;Metalama 2026.0 downloads page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article covers what we shipped this month. For the complete list of changes in Metalama 2026.0, see the &lt;a href="https://doc.metalama.net/preview/conceptual/release-notes/release-notes-2026-0"&gt;release notes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the full list of fixes and enhancements, see the changelogs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.4-preview"&gt;2026.0.4-preview&lt;/a&gt; (November 8).&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.5-preview"&gt;2026.0.5-preview&lt;/a&gt; (November 12).&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.6-rc"&gt;2026.0.6-rc&lt;/a&gt; (November 28).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="c-14-extension-members"&gt;C# 14 Extension Members&lt;/h2&gt;

&lt;p&gt;This month, we completed support for C# 14 extension blocks. You can now advise extension members and introduce members into existing extension blocks.&lt;/p&gt;

&lt;p&gt;Extension blocks are the “extend everything” feature in C# 14. Here’s how Metalama models them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Extension blocks use the &lt;code class="language-plaintext highlighter-rouge"&gt;IExtensionBlock&lt;/code&gt; interface, which implements &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType&lt;/code&gt; (&lt;code class="language-plaintext highlighter-rouge"&gt;TypeKind = Extension&lt;/code&gt;) so you can discover members the usual &lt;code class="language-plaintext highlighter-rouge"&gt;Fields&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;Properties&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;Methods&lt;/code&gt;, … collections.&lt;/li&gt;
  &lt;li&gt;They appear under &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType.ExtensionBlocks&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Extension methods and property accessors are also listed in &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType.Methods&lt;/code&gt; as implicitly-implemented methods.&lt;/li&gt;
  &lt;li&gt;You can override extension members using the advising API or add aspects using custom attributes.&lt;/li&gt;
  &lt;li&gt;You can also add new extension members to existing extension blocks.&lt;/li&gt;
  &lt;li&gt;Introducing new extension blocks won’t be supported in 2026.0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="other-improvements"&gt;Other Improvements&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Single-file applications.&lt;/strong&gt; Metalama aspects now work in single-file &lt;code class="language-plaintext highlighter-rouge"&gt;dotnet run&lt;/code&gt; applications: arguably a niche scenario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toast notifications.&lt;/strong&gt; Product briefs and news now appear via toast notifications on Windows. You can disable or configure this feature through the UI or CLI tool.&lt;/p&gt;

&lt;h2 id="visual-studio-tools-improved-stability-and-performance"&gt;Visual Studio Tools improved stability and performance&lt;/h2&gt;

&lt;p&gt;During the last two weeks, we focused heavily on Visual Studio extension stability, and uncovered more issues than expected.&lt;/p&gt;

&lt;p&gt;We owe you an apology. Most of these bugs were already present in the 2025.1 release, though some may have been introduced by a Visual Studio 17.x minor update (we don’t repeat our full manual test suite after each VS release, so we can’t be certain). We discovered them through exhaustive testing on clean virtual machines as part of our RC validation process.&lt;/p&gt;

&lt;p&gt;This level of testing clearly wasn’t done properly for 2025.1. To ensure quality meets our standards, we chose to delay the release by one month, missing our target to reach GA before the Christmas break. It was the right call.&lt;/p&gt;

&lt;p&gt;The good news: the Visual Studio extension is now significantly more stable, and we improved performance along the way. You can download it from the &lt;a href="https://www.postsharp.net/downloads/metalama/metalama-2026.0"&gt;Metalama 2026.0 downloads page&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="known-issues"&gt;Known Issues&lt;/h2&gt;

&lt;p&gt;A couple of items to address before GA:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;There’s a known issue (&lt;a href="https://github.com/metalama/Metalama.Compiler/issues/149"&gt;#149&lt;/a&gt;) with the &lt;code class="language-plaintext highlighter-rouge"&gt;apt&lt;/code&gt; package on Linux. We’re working on a fix.&lt;/li&gt;
  &lt;li&gt;The next RC will update to Roslyn 5.0.0 GA (&lt;a href="https://github.com/metalama/Metalama/issues/1212"&gt;#1212&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="whats-next"&gt;What’s Next&lt;/h2&gt;

&lt;p&gt;With full .NET 10 SDK and C# 14 support (except for by-design limitations documented in the &lt;a href="https://doc.metalama.net/preview/conceptual/release-notes/release-notes-2026-0"&gt;release notes&lt;/a&gt;), and a significantly more stable Visual Studio extension, we’re on track for GA on January 5th, 2026.&lt;/p&gt;

&lt;p&gt;If you haven’t tried the RC yet, now is the time. Test it with your projects and let us know what you find. Your feedback helps us ship a stable release.&lt;/p&gt;

&lt;p&gt;Happy meta-programming!&lt;/p&gt;

&lt;p&gt;-gael&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/status-2025-11"&gt;Metalama November 2025 Update: Release Candidate for Metalama 2026.0&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Sun, 30 Nov 2025 07:00:01 +0000</pubDate>
        
        <link>https://postsharp.net/blog/status-2025-11</link>
        <guid isPermaLink="true">https://postsharp.net/blog/status-2025-11</guid>
        
        <category>featured</category>
        
        
        <category>Metalama</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2025/2025-11-status/november-dark.svg</url>
          
        </image>
      </item>
    
      <item>
        <title>Metalama October 2025 Update: Full Steam Ahead Toward 2026.0</title>
        <description>
           
          &lt;img src="https://postsharp.net/assets/images/2025/2025-10-status/october-dark.svg" style="width: 100%"/&gt;
          
          &lt;p&gt;October is traditionally our busiest month. Microsoft ships the RCs of the .NET SDK, C# and Visual Studio, and we sprint to make Metalama ready. This year is no exception: we’ve made solid progress, and we’re confident we’ll be ready on time for the GA wave. Below is a recap of what we shipped and what’s coming.&lt;/p&gt;

&lt;h2 id="metalama-20252-skipped-for-simplicity"&gt;Metalama 2025.2: skipped for simplicity&lt;/h2&gt;

&lt;p&gt;We’ve decided not to ship Metalama 2025.2 as a standalone release. All its features, including the ability to override event handler invocations, are already merged into Metalama 2026.0. Why? The only real benefit of releasing 2025.2 would be making event-invocation advising available a few weeks earlier, but supporting two versions in parallel would slow us down.&lt;/p&gt;

&lt;h2 id="metalama-20260-almost-there"&gt;Metalama 2026.0: almost there&lt;/h2&gt;

&lt;p&gt;We are now almost code-complete with Metalama 2026.0, and have released several previews:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.3-preview"&gt;2026.0.3-preview changelog&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.2-preview"&gt;2026.0.2-preview changelog&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/metalama/Metalama/releases/tag/release%2F2026.0.1-preview"&gt;2026.0.1-preview changelog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="net-10-sdk-and-c-14"&gt;.NET 10 SDK and C# 14&lt;/h3&gt;

&lt;p&gt;We’re nearly done with support for the .NET 10 SDK and C# 14. Want to see where things stand? Check out these resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://github.com/orgs/metalama/projects/1/views/2?sliceBy%5Bvalue%5D=2026.0"&gt;General status board&lt;/a&gt; highlighting the leftovers for the 2026.0 version.&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/orgs/metalama/projects/1/views/14"&gt;C# 14 status board&lt;/a&gt; showing our progress with C# 14 features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out of scope for Metalama 2026.0:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Using null-conditional assignments in templates. &lt;a href="https://github.com/metalama/Metalama/issues/1109"&gt;#1109&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Using the &lt;code class="language-plaintext highlighter-rouge"&gt;field&lt;/code&gt; keyword in templates. &lt;a href="https://github.com/metalama/Metalama/issues/1114"&gt;#1114&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Introducing new extension blocks. &lt;a href="https://github.com/metalama/Metalama/issues/1159"&gt;#1159&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;del&gt;Introducing extension members into existing extension blocks. &lt;a href="https://github.com/metalama/Metalama/issues/1160"&gt;#1160&lt;/a&gt;&lt;/del&gt; UPDATE: implemented in 2026.0.4-preview.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fun fact&lt;/strong&gt;: we noticed that compiling our &lt;a href="https://github.com/metalama/Metalama.Tests.NopCommerce"&gt;NopCommerce snapshot&lt;/a&gt; with C# 14 breaks LINQ-to-SQL because of implicit conversions of arrays to &lt;code class="language-plaintext highlighter-rouge"&gt;ReadOnlySpan&lt;/code&gt;. We’ve had to pin this solution to C# 13 to work around this issue.&lt;/p&gt;

&lt;h3 id="extension-blocks"&gt;Extension blocks&lt;/h3&gt;

&lt;p&gt;“Extend everything” is the headline feature of C# 14, and we’re making sure Metalama supports it out of the box, including overriding extension members. Here’s how we’re modeling extension blocks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Extension blocks use the &lt;code class="language-plaintext highlighter-rouge"&gt;IExtensionBlock&lt;/code&gt; interface (&lt;code class="language-plaintext highlighter-rouge"&gt;TypeKind = Extension&lt;/code&gt;). It derives from &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType&lt;/code&gt;, and adds two properties: &lt;code class="language-plaintext highlighter-rouge"&gt;ReceiverParameter&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;ReceiverType&lt;/code&gt;. Although extension blocks derive from &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType&lt;/code&gt;, you can’t always treat them like regular ones. Yes, this technically bends the Liskov Substitution Principle, but splitting the hierarchy would make the API noisier for little practical benefit. For extension members (except classic extension methods), &lt;code class="language-plaintext highlighter-rouge"&gt;IMember.DeclaringType&lt;/code&gt; points to the &lt;code class="language-plaintext highlighter-rouge"&gt;IExtensionBlock&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Extension blocks show up under &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType.ExtensionBlocks&lt;/code&gt;, not as nested types (&lt;code class="language-plaintext highlighter-rouge"&gt;INamedType.Types&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Extension methods and property accessors are listed in &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType.Methods&lt;/code&gt; as implicitly-implemented methods. They exist in IL and are addressable in C#, but you can’t override them with Metalama.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach feels natural for C# developers and closely matches Roslyn’s model.&lt;/p&gt;

&lt;h3 id="first-class-support-for-named-tuples"&gt;First-class support for named tuples&lt;/h3&gt;

&lt;p&gt;Previously, Metalama treated tuples as plain &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType&lt;/code&gt; objects, without access to element types or names. This made scenarios like packing method arguments for interceptors (think caching) awkward and inefficient. Now, with the new &lt;code class="language-plaintext highlighter-rouge"&gt;ITupleType&lt;/code&gt; interface, you get direct access to element names and types. Create named tuples with &lt;code class="language-plaintext highlighter-rouge"&gt;TypeFactory.CreateTupleType&lt;/code&gt;, and instantiate them with &lt;code class="language-plaintext highlighter-rouge"&gt;ITupleType.CreateCreateInstanceExpression&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our implementation even supports tuples with zero or one element. For two or more, you get the nice tuple syntax; for degenerate cases, Metalama falls back to &lt;code class="language-plaintext highlighter-rouge"&gt;ValueTuple.Create(...)&lt;/code&gt;. When you use &lt;code class="language-plaintext highlighter-rouge"&gt;ITupleType&lt;/code&gt;, you don’t have to worry about these details yourself.&lt;/p&gt;

&lt;p&gt;Example: Want to advise multiple methods and pass all arguments to an observer? Here’s how:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create the tuple type.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tupleType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TypeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateTupleType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create the tuple instance.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tupleExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tupleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCreateInstanceExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Invoke the interceptor.&lt;/span&gt;
&lt;span class="n"&gt;_myObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnMethodCalled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeclaringType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tupleExpression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Transformed code:&lt;/p&gt;

&lt;div class="language-csharp highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Two or more arguments: tuple syntax&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Hat&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;FindHats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_myObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnMethodCalled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HatStore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"FindHats"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// One argument: ValueTuple.Create&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsHatAvailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;hatId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_myObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnMethodCalled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HatStore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"IsHatAvailable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ValueTuple&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hatId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Zero arguments&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsStoreOpen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_myObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnMethodCalled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HatStore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"IsStoreOpen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ValueTuple&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class="note"&gt;&lt;strong&gt;Breaking Change:&lt;/strong&gt; &lt;code class="language-plaintext highlighter-rouge"&gt;INamedType.TypeKind&lt;/code&gt; now returns &lt;code class="language-plaintext highlighter-rouge"&gt;Tuple&lt;/code&gt; instead of &lt;code class="language-plaintext highlighter-rouge"&gt;NamedType&lt;/code&gt; for tuples.&lt;/p&gt;

&lt;h3 id="advising-event-invocations"&gt;Advising event invocations&lt;/h3&gt;

&lt;p&gt;Overriding event handler invocation logic, originally planned for 2025.2, is now part of 2026.0. For details, see the &lt;a href="/blog/status-2025-09#metalama-20252-rc-event-invocation-overrides"&gt;September 2025 Status Update&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="other-improvements"&gt;Other improvements&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Introducing user-defined &lt;code class="language-plaintext highlighter-rouge"&gt;checked&lt;/code&gt; operators.&lt;/li&gt;
  &lt;li&gt;Dependency injection: pull constructor parameters across projects.&lt;/li&gt;
  &lt;li&gt;API cleanup (some breaking changes, &lt;code class="language-plaintext highlighter-rouge"&gt;[Obsolete]&lt;/code&gt; warnings).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="metalama-20251-now-supports-visual-studio-2026"&gt;Metalama 2025.1: now supports Visual Studio 2026&lt;/h2&gt;

&lt;p&gt;Last month, we added limited .NET 10 SDK support to Metalama 2025.1. As of October 7, Visual Studio 2026 is also supported.&lt;/p&gt;

&lt;p&gt;Just make sure to update both the NuGet packages and the VSIX extension.&lt;/p&gt;

&lt;h2 id="roadmap-finalizing-20260"&gt;Roadmap: finalizing 2026.0&lt;/h2&gt;

&lt;p&gt;We’re wrapping up the last features for full C# 14 support in Metalama 2026.0. Next up: stabilization, code cleanup, manual testing, and performance validation.&lt;/p&gt;

&lt;p&gt;We’re aiming for a release candidate (RC) on November 7, barring any surprises during testing. The final GA release will follow soon after Microsoft ships the GA versions of the .NET SDK and related packages.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In October, we made good progress on Metalama 2026.0, whose main focus is to support the .NET 10 SDK, C# 14, and Visual Studio 2026. We’re almost code complete, and we’ll spend most of November stabilizing the release.&lt;/p&gt;

&lt;p&gt;Happy meta-programming!&lt;/p&gt;

&lt;p&gt;-gael&lt;/p&gt;


          &lt;p&gt;
            This article was first published on a &lt;a href="https://postsharp.net/blog"&gt;https://postsharp.net/blog&lt;/a&gt;
            under the title &lt;a href="https://postsharp.net/blog/status-2025-10"&gt;Metalama October 2025 Update: Full Steam Ahead Toward 2026.0&lt;/a&gt;.
          &lt;/p&gt;
        </description>
        <pubDate>Thu, 30 Oct 2025 07:00:01 +0000</pubDate>
        
        <link>https://postsharp.net/blog/status-2025-10</link>
        <guid isPermaLink="true">https://postsharp.net/blog/status-2025-10</guid>
        
        <category>featured</category>
        
        
        <category>Metalama</category>
        
        <image>
          
          <url>https://postsharp.net/assets/images/2025/2025-10-status/october-dark.svg</url>
          
        </image>
      </item>
    
  </channel>
</rss>