PicoAop.Gen 2026.1.1

dotnet add package PicoAop.Gen --version 2026.1.1
                    
NuGet\Install-Package PicoAop.Gen -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.Gen" Version="2026.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PicoAop.Gen" Version="2026.1.1" />
                    
Directory.Packages.props
<PackageReference Include="PicoAop.Gen" />
                    
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.Gen --version 2026.1.1
                    
#r "nuget: PicoAop.Gen, 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.Gen@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.Gen&version=2026.1.1
                    
Install as a Cake Addin
#tool nuget:?package=PicoAop.Gen&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 registrations — ModuleInitializer 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

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.1.1 120 6/4/2026