TechTeaStudio.Injector.Extensions.DependencyInjection 0.3.3

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

<p align="center"> <img src="https://raw.githubusercontent.com/TechTeaStudio/Injector/product/icon.png" alt="TechTeaStudio.Injector logo" width="160" /> </p>

<h1 align="center">TechTeaStudio.Injector</h1>

<p align="center"> Lightweight, expression-tree-compiled dependency injection container for .NET 8 / 9 / 10. Open generics, decorators, lifecycle hooks, async disposal, child scopes, Lazy / Func / Owned, plus a Microsoft.Extensions.DependencyInjection sidecar for ASP.NET Core, Generic Host, and EF Core integration. </p>

<p align="center"> <a href="https://www.nuget.org/packages/TechTeaStudio.Injector"><img alt="NuGet (core)" src="https://img.shields.io/nuget/v/TechTeaStudio.Injector.svg?logo=nuget&label=NuGet" /></a> <a href="https://www.nuget.org/packages/TechTeaStudio.Injector"><img alt="Downloads" src="https://img.shields.io/nuget/dt/TechTeaStudio.Injector.svg?logo=nuget&label=Downloads" /></a> <a href="https://www.nuget.org/packages/TechTeaStudio.Injector.Extensions.DependencyInjection"><img alt="NuGet (MEDI sidecar)" src="https://img.shields.io/nuget/v/TechTeaStudio.Injector.Extensions.DependencyInjection.svg?logo=nuget&label=NuGet%20MEDI" /></a> <img alt=".NET" src="https://img.shields.io/badge/.NET-8.0%20%7C%209.0%20%7C%2010.0-512BD4?logo=dotnet&logoColor=white" /> <a href="https://github.com/TechTeaStudio/Injector/actions/workflows/dotnet.yml"><img alt="Build" src="https://img.shields.io/github/actions/workflow/status/TechTeaStudio/Injector/dotnet.yml?branch=product&logo=github&label=build" /></a> <a href="LICENSE.txt"><img alt="License" src="https://img.shields.io/badge/license-MIT-blue.svg" /></a> </p>

Overview

TechTeaStudio.Injector is a from-scratch dependency injection container for .NET. It compiles per-registration activators as expression-tree lambdas, caches them, and resolves with a single virtual call plus a delegate invoke. The public surface is small and the implementation has no transitive dependency on Microsoft.Extensions.DependencyInjection, so it drops into class libraries, CLI tools, desktop apps, plugins, and games without dragging the hosting stack along with it.

For ASP.NET Core, Generic Host, and EF Core the sister package TechTeaStudio.Injector.Extensions.DependencyInjection adapts the container to IServiceProvider, IServiceScopeFactory, IServiceProviderFactory<IContainerBuilder>, IServiceProviderIsService, IServiceProviderIsKeyedService, and IKeyedServiceProvider. The same library backs both worlds.

When to reach for it

You want this library when you need a real DI container with Autofac's feature set but Microsoft.Extensions.DependencyInjection's footprint. Concretely:

  • A class library, plugin, CLI, or desktop / game app where pulling Microsoft.Extensions.* into a leaf module is overkill.
  • A web API where you want decorators, lifecycle hooks, child scopes with extra registrations, and Owned<T> ownership transfer without bolting Scrutor and Autofac onto MEDI.
  • A codebase that values an explicit [Inject] opt-in for property and method injection over conventions.
  • An ASP.NET Core / Generic Host service where you'd swap MEDI's container under the hood and keep the rest of your code unchanged. The sidecar handles that.

You probably don't want this library when you need NativeAOT or trimming: the container compiles expression trees and uses reflection at runtime, so it is not AOT/trim-compatible. The public API is annotated with [RequiresDynamicCode] / [RequiresUnreferencedCode], so compiling under PublishAot / PublishTrimmed surfaces accurate analyzer warnings instead of silent runtime failures; for those scenarios use a compile-time DI approach. You also may not want it when MEDI's exact feature set already covers everything you do and you don't want decorators / hooks / Owned, or when your team is already invested in Autofac and switching containers is not worth the migration cost.

How it compares

Capability Injector Autofac MEDI
Constructor injection yes yes yes
Property / method injection yes ([Inject]) yes (PropertiesAutowired) no
Decorators (built-in) yes (compiled) yes no (Scrutor needed)
Modules / assembly scan yes yes no (Scrutor needed)
Open generics yes yes yes
Lazy<T> / Func<T> / Func<P, T> yes (auto) yes no
Owned<T> ownership transfer yes yes no
Lifecycle hooks (OnActivating / OnActivated) yes yes no
Child scopes with extra registrations yes yes no
IEnumerable<T> / IReadOnlyDictionary<TKey,T> yes yes partial (IEnumerable<T> only)
Validate-on-build yes (opt-in) partial yes
IAsyncDisposable yes yes yes
Circular detection with full chain yes partial partial
ASP.NET Core / Hosting integration via sidecar via Autofac.Extensions.DependencyInjection native
Keyed services (ResolveKeyed core + .NET 8 [FromKeyedServices] sidecar) yes yes yes
NativeAOT / trimming annotated (RequiresDynamicCode / RequiresUnreferencedCode); not runtime-compatible partial yes

The honest pitch: Injector sits between "MEDI plus Scrutor" and "a full Autofac setup". If you've ever installed Scrutor for decorators, then realised you also wanted lifecycle hooks and Owned<T>, this is the package that already covers both without the Autofac surface area.

Performance

A BenchmarkDotNet micro-benchmark suite lives in benchmarks/TechTeaStudio.Injector.Benchmarks. It pits TechTeaStudio.Injector against Microsoft.Extensions.DependencyInjection (MEDI) and Autofac over an identical object graph: a 3-level transient constructor chain, a scoped service with a dependency, a singleton, an open generic (IRepository<T> → Repository<T>), and a full container build. Each scenario runs one benchmark per container with a [MemoryDiagnoser].

Run it yourself (the project ships a short job of 3 warmup plus 5 measured iterations, so the whole suite finishes in a couple of minutes):

dotnet run -c Release --project benchmarks/TechTeaStudio.Injector.Benchmarks --filter '*'

Numbers below were measured on an AMD Ryzen 7 8845HS, Windows 11, .NET 10.0.7 (X64 RyuJIT), BenchmarkDotNet v0.15.8, on 2026-05-29 with that short job. Mean is per-resolve (or per-build) wall time, Alloc is managed bytes allocated per operation. Lower is better on both.

Scenario Injector MEDI Autofac
Transient resolve (3-level chain) 333 ns / 984 B 21 ns / 72 B 652 ns / 1760 B
Scoped resolve (create scope + resolve) 319 ns / 1184 B 108 ns / 360 B 1134 ns / 4344 B
Singleton resolve 50 ns / 152 B 7 ns / 0 B 114 ns / 656 B
Open-generic resolve (IRepository<T>) 211 ns / 536 B 13 ns / 24 B 190 ns / 832 B
Container build / compile 554 ns / 3.57 KB 1305 ns / 6.38 KB 11615 ns / 28.54 KB

Reading the table: MEDI wins every resolution scenario because it is a hosting-optimized container with a hand-tuned fast path and minimal feature surface, which is exactly the trade-off described in How it compares. Against Autofac, the closest match on features, Injector resolves transients, scoped services, and singletons faster with fewer allocations, lands roughly even on open generics, and builds the container an order of magnitude faster.

These micro-benchmarks are indicative, not a guarantee: the short job above trades precision for speed (note the wide error bars at this iteration count), the graph is deliberately tiny, and real-application resolution cost is dominated by your own constructors and scope topology. Re-run the suite on your hardware with a longer job before drawing conclusions for your workload.

Install

Core container:

dotnet add package TechTeaStudio.Injector

For ASP.NET Core / Generic Host / EF Core integration, add the sidecar:

dotnet add package TechTeaStudio.Injector.Extensions.DependencyInjection

Or pin specific versions in .csproj:

<PackageReference Include="TechTeaStudio.Injector" Version="0.4.0" />
<PackageReference Include="TechTeaStudio.Injector.Extensions.DependencyInjection" Version="0.3.2" />

Quick start (core)

using TechTeaStudio.Injector;

var builder = new ContainerBuilder();

builder.Register<ILogger, ConsoleLogger>().AsSingleton();
builder.Register<IGreeter, Greeter>().AsTransient();
builder.RegisterDecorator<IGreeter, TimingGreeterDecorator>();

// Open generics:
builder.Register(typeof(IRepository<>), typeof(Repository<>)).AsScoped();

// Lifecycle hooks:
builder.Register<IDbInit, DbInit>()
       .AsSingleton()
       .OnActivated(_ => Console.WriteLine("DbInit constructed"));

await using IContainer container = builder.Build(new BuildOptions { ValidateOnBuild = true });

var greeter = container.Resolve<IGreeter>();
greeter.Greet("World");

await using (IScope scope = container.CreateScope())
{
    var repo = scope.Resolve<IRepository<User>>();   // closed-on-demand
    var lazy = scope.Resolve<Lazy<IGreeter>>();      // auto-wrapped

    using (Owned<IReportBuilder> owned = scope.Resolve<Owned<IReportBuilder>>())
    {
        owned.Value.Build();
    } // releases the owned graph
}

Quick start (ASP.NET Core / Generic Host)

using Microsoft.Extensions.Hosting;
using TechTeaStudio.Injector;
using TechTeaStudio.Injector.Extensions.DependencyInjection;

var host = Host.CreateApplicationBuilder(args);

// Standard IServiceCollection registrations still work:
host.Services.AddSingleton<ILogger, ConsoleLogger>();

// Swap the host's container with Injector and add Injector-only registrations on top:
host.ConfigureContainer(
    new InjectorServiceProviderFactory(new BuildOptions { ValidateOnBuild = true }),
    builder =>
    {
        builder.Register(typeof(IRepository<>), typeof(Repository<>)).AsScoped();
        builder.RegisterDecorator<IGreeter, TimingGreeterDecorator>();
    });

using var app = host.Build();
// ASP.NET Core, EF Core, and Hosting now resolve every service through TechTeaStudio.Injector.

Lifetimes at a glance

Lifetime Per-resolution behaviour When to use
AsTransient() New instance every call. Default for cheap, stateless services.
AsScoped() Same instance within a single IScope. Per-request / per-operation state.
AsSingleton() Single instance for the whole container's lifetime. Caches, configuration, expensive shared deps.

Feature notes

Open generics

Register an open generic implementation against an open generic service. The container synthesizes the closed registration on demand and caches it.

builder.Register(typeof(IRepository<>), typeof(Repository<>)).AsScoped();

var users  = scope.Resolve<IRepository<User>>();
var orders = scope.Resolve<IRepository<Order>>();
// Two distinct closed registrations, two distinct scoped instances per scope.

Closed-generic registrations take precedence when both are present.

Lazy<T> / Func<T> / Func<P, T> auto-resolution

builder.Register<IFoo, Foo>().AsTransient();

var lazy   = container.Resolve<Lazy<IFoo>>();           // deferred resolution
var factory = container.Resolve<Func<IFoo>>();           // delegate
var paramFactory = container.Resolve<Func<string, IFoo>>(); // ctor takes a string + resolved deps

No explicit registration is needed for any of these.

Owned<T> ownership transfer

using (Owned<IReportBuilder> owned = container.Resolve<Owned<IReportBuilder>>())
{
    owned.Value.Build();
} // disposing the Owned<T> disposes the inner scope and everything it pulled in

The inner scope is parented at the active resolver, independent of the caller's scope lifetime.

Lifecycle hooks (OnActivating / OnActivated)

builder.Register<IFoo, Foo>()
       .OnActivating(f => f.Configure())     // before property / method injection
       .OnActivated (f => f.Initialize());   // after property / method injection

Hooks fire once per activation: once total for singletons, on every resolve for transients.

Child scopes with extra registrations

using var scope = container.CreateScope();

using (var child = scope.BeginLifetimeScope(b => b.RegisterInstance(currentRequest)))
{
    var handler = child.Resolve<IRequestHandler>(); // sees currentRequest + every parent registration
}

Disposing the child disposes only what the child created. The parent keeps running.

Validate-on-build

var container = builder.Build(new BuildOptions { ValidateOnBuild = true });
// AggregateException at Build() if any registration has a missing or cyclic dependency

The validator walks every non-open-generic registration in a throwaway scope and aggregates failures.

Decorators with compiled activators

builder.Register<IGreeter, Greeter>().AsTransient();
builder.RegisterDecorator<IGreeter, TimingGreeterDecorator>();
builder.RegisterDecorator<IGreeter, LoggingGreeterDecorator>();

var g = container.Resolve<IGreeter>();
// g is new LoggingGreeterDecorator(new TimingGreeterDecorator(new Greeter()))

Each decorator activator is built as an Expression.Lambda<Func<IResolver, object, object>>, compiled once, cached per registration. Build-time validation rejects a decorator without a matching base registration.

Open-generic decorators use the Type-based overload and wrap every closed resolution of the service, whether the base was registered open or closed:

builder.Register(typeof(IRepository<>), typeof(Repository<>));
builder.RegisterDecorator(typeof(IRepository<>), typeof(LoggingRepository<>));

var users = container.Resolve<IRepository<User>>(); // LoggingRepository<User> around Repository<User>

Modules and assembly scanning

public sealed class LoggingModule : IModule
{
    public void Register(IContainerBuilder b) =>
        b.Register<ILogger, ConsoleLogger>().AsSingleton();
}

builder.RegisterModule(new LoggingModule());
builder.RegisterAssembly(typeof(Program).Assembly, t => t.Name.EndsWith("Handler"));

RegisterAssembly walks every concrete public class matching the predicate and binds it to itself plus every interface it implements (skipping IDisposable and IAsyncDisposable).

Property and method injection

public sealed class Worker
{
    [Inject] public ILogger? Logger { get; set; }

    [Inject]
    public void Initialise(IService service) { /* ... */ }
}

The injection passes run after construction (property first, then method). Without the attribute, neither runs; explicit opt-in keeps reflection cost minimal.

Collection resolution

var all = scope.ResolveAll<IHandler>();                  // every unkeyed registration of IHandler
var byKey = scope.ResolveDictionary<string, IHandler>(); // only the WithKey(...) ones
var one = scope.ResolveKeyed<IHandler>("alpha");         // a single WithKey(...) registration

Injectable as constructor parameters too: IEnumerable<IHandler>, IReadOnlyList<IHandler>, IReadOnlyDictionary<string, IHandler>.

Keyed registrations follow Microsoft.Extensions.DependencyInjection semantics: a WithKey(...) registration is never the default for Resolve<T>(), is not part of IEnumerable<T>, and is reachable only through ResolveKeyed / TryResolveKeyed / ResolveDictionary.

Public API (selected)

namespace TechTeaStudio.Injector;

public interface IContainerBuilder
{
    IRegistrationBuilder Register<TService, TImpl>() where TImpl : class, TService where TService : class;
    IRegistrationBuilder Register<TService>() where TService : class;
    IRegistrationBuilder Register<TService>(Func<IResolver, TService> factory) where TService : class;
    IRegistrationBuilder Register(Type serviceType, Type implementationType);
    IRegistrationBuilder Register(Type serviceType);
    IRegistrationBuilder RegisterInstance<TService>(TService instance) where TService : class;
    IContainerBuilder    RegisterDecorator<TService, TDecorator>() where TDecorator : class, TService where TService : class;
    IContainerBuilder    RegisterDecorator(Type serviceType, Type decoratorType);
    IContainerBuilder    RegisterModule(IModule module);
    IContainerBuilder    RegisterAssembly(Assembly asm, Predicate<Type>? filter = null, Lifetime lifetime = Lifetime.Transient);
    IContainer           Build();
    IContainer           Build(BuildOptions options);
}

public interface IRegistrationBuilder
{
    IRegistrationBuilder AsTransient();
    IRegistrationBuilder AsScoped();
    IRegistrationBuilder AsSingleton();
    IRegistrationBuilder WithKey(object key);
    IRegistrationBuilder As(params Type[] additionalServiceTypes);
    IRegistrationBuilder OnActivating(Action<object> hook);
    IRegistrationBuilder OnActivated (Action<object> hook);
}

public interface IResolver
{
    T                          Resolve<T>();
    object                     Resolve(Type serviceType);
    IEnumerable<T>             ResolveAll<T>();
    IEnumerable<object>        ResolveAll(Type serviceType);
    IDictionary<TKey, T>       ResolveDictionary<TKey, T>() where TKey : notnull;

    T                          ResolveKeyed<T>(object key);
    object                     ResolveKeyed(Type serviceType, object key);
    bool                       TryResolveKeyed<T>(object key, out T value);
    bool                       TryResolve<T>(out T value);
    bool                       TryResolve(Type serviceType, out object? value);
    T?                         ResolveOptional<T>();
    object?                    ResolveOptional(Type serviceType);
}

public interface IContainer : IResolver, IDisposable, IAsyncDisposable
{
    IScope CreateScope();
}

public interface IScope : IResolver, IDisposable, IAsyncDisposable
{
    IScope BeginLifetimeScope(Action<IContainerBuilder> configure);
}

For the MEDI sidecar:

namespace TechTeaStudio.Injector.Extensions.DependencyInjection;

public sealed class InjectorServiceProvider :
    IServiceProvider, IServiceProviderIsService, IServiceProviderIsKeyedService,
    IKeyedServiceProvider, IDisposable, IAsyncDisposable { /* ... */ }

public sealed class InjectorServiceProviderFactory : IServiceProviderFactory<IContainerBuilder>
{
    public InjectorServiceProviderFactory();
    public InjectorServiceProviderFactory(BuildOptions? buildOptions);
    public IContainerBuilder CreateBuilder(IServiceCollection services);
    public IServiceProvider CreateServiceProvider(IContainerBuilder builder);
}

public static class ServiceCollectionExtensions
{
    public static IContainerBuilder PopulateFromServiceCollection(this IContainerBuilder b, IServiceCollection s);
    public static IServiceProvider  AsServiceProvider(this IContainer container);
}

Project layout

Injector/
├── src/TechTeaStudio.Injector/                                <- core NuGet package
│   ├── ContainerBuilder.cs, Container.cs, Scope.cs            <- registration + resolution + scoping
│   ├── Activators.cs, Registration.cs                         <- expression-tree compilation
│   ├── BuildOptions.cs, Owned.cs, DisposalHelpers.cs          <- v0.2 additions
│   ├── ChildScopeRegistry.cs                                  <- child-scope-extra registrations
│   ├── IContainerBuilder.cs, IResolver.cs, IContainer.cs,
│   │   IScope.cs, IModule.cs, Lifetime.cs, InjectAttribute.cs,
│   │   Exceptions.cs                                          <- public surface
│   └── AssemblyInfo.cs                                        <- InternalsVisibleTo sidecar
├── src/TechTeaStudio.Injector.Extensions.DependencyInjection/ <- MEDI sidecar package
│   ├── InjectorServiceProvider.cs
│   ├── InjectorServiceScope.cs, InjectorServiceScopeFactory.cs
│   ├── InjectorServiceProviderFactory.cs
│   ├── ServiceCollectionExtensions.cs
│   └── KeyedResolutionHelpers.cs
├── samples/TechTeaStudio.Injector.Sample/                     <- console demo (not packaged)
├── tests/TechTeaStudio.Injector.Tests/                        <- 82 xUnit tests
├── tests/TechTeaStudio.Injector.Extensions.DependencyInjection.Tests/ <- 45 sidecar tests
├── .github/workflows/dotnet.yml                               <- shared TTS NuGet publish workflow
├── CHANGELOG.md
├── LICENSE.txt
└── README.md

Build & test

dotnet build Injector.slnx
dotnet test  Injector.slnx

Requires .NET SDK 10. The core library and the sidecar both multi-target net8.0;net9.0;net10.0. The sample and the test projects are net10.0 only. 127 tests pass across the two test projects (82 core + 45 sidecar) with 0 warnings on Release.

Versioning & release

Two <Version> declarations live in the respective .csproj files: src/TechTeaStudio.Injector/TechTeaStudio.Injector.csproj for the core, src/TechTeaStudio.Injector.Extensions.DependencyInjection/TechTeaStudio.Injector.Extensions.DependencyInjection.csproj for the sidecar. Both follow 3-part SemVer (X.Y.Z). Bump rules:

  • Bug fix: Z + 1
  • New feature, source-compatible: Y + 1, reset Z = 0
  • Breaking change after 1.0: X + 1, reset Y = Z = 0

Commit format is vX.Y.Z <short description>. Push to product triggers the shared TechTeaStudio NuGet publish workflow at .github/workflows/dotnet.yml, which packs every project matched by src/*/*.csproj and pushes to nuget.org with --skip-duplicate. Never push to nuget.org manually.

See CHANGELOG.md for the full release history.

Further reading

License

Licensed under the MIT License. Copyright © Tech Tea Studio.

<p align="center"> Built as part of the Hyperion Ecosystem by <a href="https://techteastudio.cc">TechTeaStudio</a>. </p>

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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 is compatible.  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 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.

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
0.3.3 84 10/2/2026
0.3.0 146 5/15/2026

v0.3.3: Rebuilt against core 0.5.0. First version published to nuget.org after 0.3.0 (0.3.1 and 0.3.2 never shipped because CI failed). Fixes the package dependency: 0.3.0 declared a dependency on core 0.3.0, which was never published, so it could not be installed; 0.3.3 depends on core 0.5.0. Keyed services registered through PopulateFromServiceCollection now follow Microsoft.Extensions.DependencyInjection semantics: a keyed descriptor is never returned by GetService(Type) / GetServices<T>() and IsService(Type) ignores it; it stays reachable via GetKeyedService / GetRequiredKeyedService.

v0.3.2: Rebuilt against core 0.4.0; the adapter exposes the new keyed/optional resolution and open-generic decorators. Documentation fix.

v0.3.1: NativeAOT / trimming diagnostics. The adapter enables the trim/AOT analyzers; methods implementing MEDI interfaces carry targeted [UnconditionalSuppressMessage] with justifications. Not trim/AOT-safe at runtime (it drives the expression-tree-compiled core).

v0.3.0: Microsoft.Extensions.DependencyInjection adapter for TechTeaStudio.Injector.

Adapters:
 * InjectorServiceProvider (IServiceProvider, IServiceProviderIsService, IServiceProviderIsKeyedService, IKeyedServiceProvider, IDisposable, IAsyncDisposable).
 * InjectorServiceScope / InjectorServiceScopeFactory.
 * InjectorServiceProviderFactory<IContainerBuilder> for hostBuilder.UseServiceProviderFactory(...).

Helpers:
 * IContainerBuilder.PopulateFromServiceCollection(IServiceCollection) — round-trips every ServiceDescriptor variant including .NET 8+ keyed services.
 * IContainer.AsServiceProvider() — convenience non-owning adapter.

IsService recognizes IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>, Lazy<T>, Func<T>, and Owned<T> when the inner type is registered.

Full changelog: https://github.com/TechTeaStudio/Injector/blob/product/CHANGELOG.md