PicoAop.Abs 2026.1.1

dotnet add package PicoAop.Abs --version 2026.1.1
                    
NuGet\Install-Package PicoAop.Abs -Version 2026.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PicoAop.Abs" Version="2026.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PicoAop.Abs" Version="2026.1.1" />
                    
Directory.Packages.props
<PackageReference Include="PicoAop.Abs" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PicoAop.Abs --version 2026.1.1
                    
#r "nuget: PicoAop.Abs, 2026.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PicoAop.Abs@2026.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PicoAop.Abs&version=2026.1.1
                    
Install as a Cake Addin
#tool nuget:?package=PicoAop.Abs&version=2026.1.1
                    
Install as a Cake Tool

PicoAop

Compile-time AOP decorator generation for PicoDI. Zero runtime proxy, zero reflection.

Quick Start

dotnet add package PicoAop
using PicoDI;
using PicoAop.Abs;
using PicoAop.DI;

var container = new SvcContainer();

// Register interceptors
container.RegisterSingleton<LoggingInterceptor>();

// Mark services for interception — source generator emits decorator classes
container.Register<IGreeter, Greeter>(SvcLifetime.Scoped)
    .InterceptBy<LoggingInterceptor>();

container.Build();
await using var scope = container.CreateScope();

var greeter = scope.GetService<IGreeter>();
var log = scope.GetService<LoggingInterceptor>();

// Generated: IGreeter_LoggingInterceptorDecorator
var decorated = new IGreeter_LoggingInterceptorDecorator(greeter, log);
Console.WriteLine(decorated.Greet("World"));

Core Concepts

IInterceptor

The interceptor contract — 4 methods for the cross-product of {sync, async} × {has result, void}:

public interface IInterceptor
{
    TResult Invoke<TResult>(IInvocation<TResult> invocation,
        Func<IInvocation<TResult>, TResult> next);

    void InvokeVoid(IInvocation<VoidResult> invocation,
        Action<IInvocation<VoidResult>> next);

    ValueTask<TResult> InvokeAsync<TResult>(IInvocation<TResult> invocation,
        Func<IInvocation<TResult>, ValueTask<TResult>> next);

    ValueTask InvokeAsyncVoid(IInvocation<VoidResult> invocation,
        Func<IInvocation<VoidResult>, ValueTask> next);
}

The 4-method split is necessary to avoid allocations: ValueTask<VoidResult> cannot be returned where ValueTask is expected (no inheritance relationship). Collapsing to 2 methods would require async state machines for pass-through void calls.

IInvocation<TResult>

Call context exposed to interceptors:

public interface IInvocation<TResult>
{
    string MethodName { get; }    // Which method was called
    Type ServiceType { get; }     // Which service type
    TResult Result { get; set; }  // Modify return value
}

InterceptorBase

Abstract base with virtual pass-through defaults. Override only the methods you need:

public sealed class LoggingInterceptor : InterceptorBase
{
    public override TResult Invoke<TResult>(
        IInvocation<TResult> inv, Func<IInvocation<TResult>, TResult> next)
    {
        Console.WriteLine($"[LOG] {inv.ServiceType.Name}.{inv.MethodName}()");
        var result = next(inv);
        Console.WriteLine($"[LOG] → {result}");
        return result;
    }
}

How It Works

PicoAop.Gen detects .InterceptBy<T>() at compile time and emits:

  1. Invocation structs — per-method value types that capture parameters and delegate to the target
  2. Decorator classes — sealed classes implementing the service interface, wrapping _inner + _i0
  3. DI registrationsModuleInitializer that auto-registers decorator chains

All wiring is resolved at build time. No DispatchProxy, no Castle.Core, no IL emit.

Decoration chain (onion model):

Interceptor B (outermost)
  └→ Interceptor A
       └→ Real implementation (innermost)
container.Register<IGreeter, Greeter>()
    .InterceptBy<LoggingInterceptor>()    // outer
    .InterceptBy<TimingInterceptor>();    // inner

// Generated: TimingDecorator(LoggingDecorator(Greeter))
// Call: Timing.Invoke → next → Logging.Invoke → next → Greeter.Greet()

Per-Service Interception

container.Register<IGreeter, Greeter>(SvcLifetime.Scoped)
    .InterceptBy<LoggingInterceptor>()
    .InterceptBy<TimingInterceptor>();

Global Interception

Apply an interceptor to all matching services:

container.AddInterceptor<MetricsInterceptor>();

Filter chain (compile-time markers, detected by source generator):

container.AddInterceptor<LoggingInterceptor>()
    .WhereNamespace("MyApp.Services")
    .WhereImplements<IValidator>()
    .Except<HealthCheck>();

Excluding Interceptors

// Exclude specific interceptors from a service
container.Register<IGreeter, Greeter>()
    .WithoutInterceptor<MetricsInterceptor>()
    .InterceptBy<LoggingInterceptor>();

// Remove all interceptors (both per-service and global)
container.Register<IHealthCheck, HealthCheck>()
    .WithoutInterceptors();

Limitations

  • ref / out / in parameters — delegated directly without interception (C# structs cannot store ref fields)
  • Generic methods — type parameter substitution not implemented
  • Properties — delegated transparently, not intercepted

Diagnostics

Code Severity Description
PICO010 Error Type in InterceptBy<T>() does not implement IInterceptor
PICO011 Error WhereImplements<T> requires an interface type
PICO012 Warning No interceptors matched for service
PICO013 Error Interceptor both globally declared and per-service excluded
PICO014 Warning InterceptBy<T>() follows multiple Register calls
PICO015 Warning Sanitized type names collide — second type skipped

Packages

Package Description
PicoAop.Abs IInterceptor, IInvocation<TResult>, InterceptorBase
PicoAop.Gen Roslyn source generator — emits decorator classes
PicoAop.DI DI extensions: InterceptBy<T>(), AddInterceptor<T>()

Thread Safety

All wiring is resolved at compile time. At runtime:

  • Interceptors must be thread-safe if registered as Singleton (shared across concurrent requests)
  • Decorators are stateless wrappers — thread safety depends on the inner service
  • Invocation structs are stack-allocated value types — never shared across threads

Comparison

PicoAop Castle.DynamicProxy Metalama
Weaving Source generator (build time) Runtime IL emit Build-time IL rewrite
AOT compatible ✅ Yes ❌ No ✅ Yes
Zero runtime reflection
Debug generated code ✅ Readable C# ❌ IL ❌ IL
Interceptor interface 4 methods IInterceptor (1 method) Override aspect class
Method interception
Property interception ❌ Delegated
ref/out/in params ❌ Delegated
Generic methods ❌ Future

← Back to PicoHex

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on PicoAop.Abs:

Package Downloads
PicoAop.DI

PicoDI registration extensions for PicoAop interceptors

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.1.1 43 6/4/2026