PicoAop.Abs
2026.1.1
dotnet add package PicoAop.Abs --version 2026.1.1
NuGet\Install-Package PicoAop.Abs -Version 2026.1.1
<PackageReference Include="PicoAop.Abs" Version="2026.1.1" />
<PackageVersion Include="PicoAop.Abs" Version="2026.1.1" />
<PackageReference Include="PicoAop.Abs" />
paket add PicoAop.Abs --version 2026.1.1
#r "nuget: PicoAop.Abs, 2026.1.1"
#:package PicoAop.Abs@2026.1.1
#addin nuget:?package=PicoAop.Abs&version=2026.1.1
#tool nuget:?package=PicoAop.Abs&version=2026.1.1
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:
- Invocation structs — per-method value types that capture parameters and delegate to the target
- Decorator classes — sealed classes implementing the service interface, wrapping
_inner+_i0 - DI registrations —
ModuleInitializerthat 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 | ✅ | ✅ |
| Product | Versions 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. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.7)
- PicoDI.Abs (>= 2026.1.1)
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 |