PicoDI.Abs 2026.10.0

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

PicoDI

Zero-reflection, AOT-compatible dependency injection for .NET.

Quick Start

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

var container = new SvcContainer();
container.RegisterSingleton<IService>(scope => new MyService());
container.Build();

await using var scope = container.CreateScope();
var svc = scope.GetService<IService>();

Registration

All Register* methods return ISvcContainer for fluent chaining.

Factory-Based (always works, no source gen required)

// Lifetime-specific factory overloads
container.RegisterSingleton<IService>(scope => new Service(scope.GetService<IDep>()));
container.RegisterScoped<IRepository>(scope => new Repository());
container.RegisterTransient<IValidator>(scope => new Validator());

// Generic Register with explicit lifetime
container.Register<ICache>(scope => new MemoryCache(), SvcLifetime.Singleton);
container.Register<ILogger, MyLogger>(scope => new MyLogger(), SvcLifetime.Scoped);

// Pre-built singleton instance
container.RegisterSingle<IClock>(SystemClock.Instance);

// Multiple registrations at once
container.RegisterRange(new[] {
    SvcDescriptor.Create<IServiceA>(scope => new ServiceA()),
    SvcDescriptor.FromInstance(typeof(IServiceB), existingInstance)
});

Type-Based via typeof() (runtime, no source gen required)

// These create a SvcDescriptor and register it at runtime
container.Register(typeof(IService), typeof(Service), SvcLifetime.Singleton);
container.RegisterScoped(typeof(IRepository), typeof(SqlRepository));
container.RegisterTransient(typeof(IValidator), typeof(EmailValidator));

// Self-type registration
container.Register(typeof(MyService), SvcLifetime.Transient);

These typeof() overloads delegate to SvcDescriptor and work with or without the source generator. They record the type mapping in the container but do not provide a factory — pair with a factory registration or the source generator for actual instance creation.

Type-Based via Generic Type Parameters (uses the embedded PicoDI.Gen analyzer)

// These compile to zero-allocation factory delegates
// The PicoDI.Gen analyzer ships embedded in PicoDI.Abs — available automatically
container.RegisterSingleton<IService, Service>();
container.RegisterScoped<IRepository, SqlRepository>();
container.RegisterTransient<IValidator, EmailValidator>();

These generic overloads are compile-time markers. PicoDI.Gen scans them and generates AOT-compatible factory code. Without the source generator they throw SourceGeneratorRequiredException at runtime.

Open Generics

// Open generic registration — closed versions resolved at compile time
container.Register(typeof(IRepository<>), typeof(SqlRepository<>), SvcLifetime.Scoped);
container.Register(typeof(ILogger<>), typeof(ConsoleLogger<>), SvcLifetime.Singleton);

Hosted Services

container.RegisterHostedSvc<BackgroundWorker>();
container.RegisterHostedSvc<HealthCheckService>(scope => new HealthCheckService(/* deps */));

Resolution

using var scope = container.CreateScope();

// Typed resolution — dictionary lookup with cached registrations
// (generated factories inline constructor chains for dependencies)
var svc = scope.GetService<IService>();
var repos = scope.GetServices<IRepository>();

// Type-based resolution
var instance = scope.GetService(typeof(IService));
var allInstances = scope.GetServices(typeof(IRepository));

// Try-pattern — no exception for missing services
if (scope.TryGetService(typeof(IOptional), out var optional))
    Console.WriteLine($"Got: {optional}");

if (scope.TryGetServices(typeof(IPlugin), out var plugins))
    foreach (var p in plugins) Console.WriteLine(p);

// IsRegistered — check before resolving
if (container.IsRegistered(typeof(IService)))
    Console.WriteLine("IService is available");

Lifetimes

Lifetime Instantiation Disposal
Transient New every resolution Tracked by resolving scope, disposed in reverse creation order (LIFO)
Scoped Once per scope Disposed when scope disposes
Singleton Once per container Disposed when container disposes

Multiple registrations for the same service type are supported. Resolution returns the last (most recent) registration. GetServices<T>() returns all registrations in order.

Singleton factories run against the container root scope. A singleton factory's scope parameter is the container-internal root scope (not the resolving scope), so dependencies resolved inside a singleton factory live until container disposal — they never die with a short-lived request scope. Scoped/transient disposables created this way are disposed by the container.

Source Generator (PicoDI.Gen)

PicoDI.Gen is embedded in PicoDI.Abs and activated automatically — no explicit package reference needed. Installing PicoDI or PicoDI.Abs is sufficient.

The generator scans all generic Register* call sites (e.g. RegisterScoped<TService, TImpl>()) in your codebase and emits:

The generator scans all Register* call sites in your codebase and emits:

  • ConfigureGeneratedServices() — factory delegates with inlined new expressions and inlined dependency chains (internal typed resolvers, pruned to referenced dependencies only)
  • Compile-time circular dependency detection — PICO002 error at build time
  • Open generic metadata — cross-assembly discovery for Register(typeof(I<>), typeof(C<>))
  • [ModuleInitializer] auto-configurator — zero-config startup, no manual Build step needed

The source generator validates implementation types at compile time and emits diagnostics:

Code Severity Description
PICO002 Error Circular dependency detected
PICO003 Error Abstract type registered as implementation
PICO004 Error Implementation type has no public constructor
PICO005 Error Multiple constructors marked with [SvcConstructor]

Mark the preferred constructor with [SvcConstructor] when the implementation has multiple constructors:

public sealed class MyService : IService
{
    [SvcConstructor] // Source generator picks this one
    public MyService(IDepA a, IDepB b) { }

    public MyService(IDepA a) { } // Ignored
}

Hosted Services

Basic: IHostedSvc

public sealed class Worker(ILogger<Worker> logger) : IHostedSvc
{
    public async Task StartAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            logger.Info("Working...");
            await Task.Delay(1000, ct);
        }
    }

    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}

Advanced: IHostedLifecycleSvc

For fine-grained lifecycle control with four-phase startup and shutdown:

public sealed class Server(ILogger<Server> logger) : IHostedLifecycleSvc
{
    public async Task StartingAsync(CancellationToken ct) { /* pre-start init */ }
    public async Task StartAsync(CancellationToken ct)       { /* bind port */ }
    public async Task StartedAsync(CancellationToken ct)     { /* post-start */ }
    public async Task StoppingAsync(CancellationToken ct)    { /* drain connections */ }
    public async Task StopAsync(CancellationToken ct)        { /* close port */ }
    public async Task StoppedAsync(CancellationToken ct)     { /* cleanup */ }
}

Convenience: BackgroundSvc

Template method pattern — override ExecuteAsync only:

public sealed class PollingService : BackgroundSvc
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await PollAsync();
            await Task.Delay(5000, stoppingToken);
        }
    }
}

SvcHostBuilder

Fluent builder for hosted services with built-in lifecycle management:

var builder = new SvcHostBuilder();
var host = await builder
    .ConfigureServices(container =>
    {
        container.RegisterSingleton<IService>(_ => new Service());
        container.RegisterHostedSvc<Worker>();
    })
    .BuildAsync();

// ... application runs ...

await host.StopAsync();

Startup order follows registration order. Shutdown reverses it (LIFO). Each hosted service is a one-shot — once stopped, it cannot be restarted without creating a new container.

Interceptor / AOP (Compile-Time Decorators)

PicoDI.Gen is embedded in PicoDI.Abs and runs automatically — no separate package reference needed. The source generator detects InterceptBy<T>() markers at compile time and emits decorator classes.

How it works

  1. Write interceptors extending InterceptorBase
  2. Register them as services in the container
  3. Chain .InterceptBy<TInterceptor>() after Register() — the source generator detects these calls at compile time and emits decorator classes
  4. Manually wire decorator chains using the generated classes
// Step 1: Define interceptors
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;
    }
}

public sealed class RetryInterceptor(int maxRetries) : InterceptorBase
{
    public override TResult Invoke<TResult>(
        IInvocation<TResult> inv, Func<IInvocation<TResult>, TResult> next)
    {
        for (var i = 1; ; i++)
        {
            try { return next(inv); }
            catch (Exception ex) when (i < maxRetries)
                => Console.WriteLine($"[RETRY] attempt {i}: {ex.Message}");
        }
    }
}
// Step 2: Register interceptors + services
var container = new SvcContainer();

container.RegisterSingleton<LoggingInterceptor>();
container.RegisterSingleton<RetryInterceptor>(_ => new RetryInterceptor(3));

// InterceptBy<T>() is a compile-time marker — detected by the source generator
container.Register<ICalculator>(_ => new Calculator(), SvcLifetime.Scoped)
    .InterceptBy<LoggingInterceptor>();
container.Register<IApiClient>(_ => new FlakyApiClient(), SvcLifetime.Scoped)
    .InterceptBy<RetryInterceptor>();

container.Build();
// Step 3: Resolve and manually build decorator chains
await using var scope = container.CreateScope();

var calc = scope.GetService<ICalculator>()!;
var api = scope.GetService<IApiClient>()!;
var log = scope.GetService<LoggingInterceptor>()!;
var retry = scope.GetService<RetryInterceptor>()!;

// Generated class names: {ServiceType}_{InterceptorType}Decorator
var calcDecorated = new ICalculator_LoggingInterceptorDecorator(calc, log);
var apiDecorated = new IApiClient_RetryInterceptorDecorator(api, retry);

calcDecorated.Add(3, 4);    // [LOG] ICalculator.Add() → 7
apiDecorated.Fetch();        // [RETRY] attempt 1: API down...

All wiring is resolved at compile time — zero reflection. See PicoDI.Sample.Aop for a complete working example with Logging, Timing, Validation, and Retry interceptors.

Error Handling

Resolution Errors

  • Unregistered service: GetService() throws PicoDiException. Use TryGetService() / TryGetServices() for optional dependencies.
  • Disposed scope/container: Throws ObjectDisposedException.
  • Source generator not applied: Throws SourceGeneratorRequiredException for type-based registrations.

Disposal Errors

Disposal is fault-tolerant — one service failing to dispose does not prevent others from being cleaned up. Errors are reported via the OnError callback:

var container = new SvcContainer();
container.OnError = (ex, context) =>
{
    Console.Error.WriteLine($"[{context}] {ex.Message}");
};

Thread Safety

PicoDI is designed for concurrent use:

  • Registration: Serialized under lock. Must complete before Build().
  • Build(): Idempotent. Freezes registration cache into lock-free FrozenDictionary.
  • Resolution: Lock-free reads. Singleton creation uses deadlock-safe double-checked locking. Scoped instances use ConcurrentDictionary + Lazy<T>.
  • Disposal: Idempotent. DisposeAsync() can be called concurrently and multiple times safely.

Packages

Package Description
PicoDI DI container runtime
PicoDI.Abs Abstractions: ISvcContainer, ISvcScope, SvcDescriptor, SvcLifetime, IHostedSvc, BackgroundSvc, SourceGeneratorRequiredException
PicoDI.Gen Roslyn source generator + diagnostic analyzer (embedded in PicoDI.Abs — no separate reference needed)

← Back to PicoInfra

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages (11)

Showing the top 5 NuGet packages that depend on PicoDI.Abs:

Package Downloads
PicoDI

A lightweight, zero-reflection, AOT-compatible dependency injection container for .NET with compile-time source generation

PicoCfg.DI

PicoDI registration helpers for PicoCfg roots, snapshots, and generated bound POCOs.

PicoAop.Abs

Abstractions for PicoAop — compile-time AOP interceptors for PicoDI

PicoLog.DI

PicoDI integration for PicoLog registration (AddPicoLog, WriteTo, ReadFrom).

PicoMediator

Compile-time command/event dispatch for PicoDI

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.10.0 375 9/18/2026
2026.9.3 397 9/15/2026
2026.9.2 149 9/14/2026
2026.9.1 308 8/24/2026
2026.9.0 515 8/23/2026
2026.8.9 225 8/22/2026
2026.8.8 225 8/22/2026
2026.8.7 219 8/21/2026
2026.8.6 219 8/21/2026
2026.8.5 282 8/16/2026
2026.8.4 229 8/13/2026
2026.8.3 210 8/8/2026
2026.8.2 278 8/5/2026
2026.8.1 213 8/5/2026
2026.8.0 211 8/5/2026
2026.7.3 683 7/12/2026
2026.7.2 228 7/12/2026
2026.7.1 230 7/12/2026
2026.6.17 229 7/12/2026
2026.6.16 235 7/9/2026
Loading failed