Generator-based Dependency Injection for .NET — elegant, fast, AOT-ready
GenDI is a dependency injection library built on top of C# source generators, providing full compatibility with NativeAOT and trimming. It works as an additional module to Microsoft.Extensions.DependencyInjection, allowing you to register services automatically at compile time — no reflection required.
Real-world services accumulate dependencies. With traditional constructor injection this tends to look like this:
// ❌ The "constructor tax" — grows every time a new dependency is added
public class OrderProcessor
{
private readonly IOrderRepository _orderRepository;
private readonly IPaymentGateway _paymentGateway;
private readonly IEmailService _emailService;
private readonly IInventoryService _inventoryService;
private readonly ILogger<OrderProcessor> _logger;
public OrderProcessor(
IOrderRepository orderRepository,
IPaymentGateway paymentGateway,
IEmailService emailService,
IInventoryService inventoryService,
ILogger<OrderProcessor> logger)
{
_orderRepository = orderRepository;
_paymentGateway = paymentGateway;
_emailService = emailService;
_inventoryService = inventoryService;
_logger = logger;
}
}With GenDI's property injection, the same class becomes clean and self-documenting:
// ✅ Declare what you need — GenDI wires everything at compile time
[Injectable<IOrderProcessor>(ServiceLifetime.Scoped)]
public class OrderProcessor : IOrderProcessor
{
[Inject] public required IOrderRepository OrderRepository { get; init; }
[Inject] public required IPaymentGateway PaymentGateway { get; init; }
[Inject] public required IEmailService EmailService { get; init; }
[Inject] public required IInventoryService InventoryService { get; init; }
[Inject] public required ILogger<OrderProcessor> Logger { get; init; }
}No private fields. No constructor ceremony. No manual wiring. Just declare your dependencies and move on.
- Property injection as first-class citizen: use
[Inject]onrequiredinit-only properties — dependencies read like documentation, not plumbing. - Zero boilerplate registration: a single
[Injectable]attribute replacesAddScoped<TImpl>()calls scattered across startup files. - Readable generated flow: activation is emitted as explicit
new+GetRequiredService<T>(), making the wiring transparent and debuggable. - Compile-time safety: the C# compiler enforces every
required[Inject]property is assigned — you cannot accidentally skip a dependency. Container registration errors (unregistered services) remain runtime exceptions, just like standard DI. - Deterministic registration order:
Group+Ordergive you predictable, testable pipeline composition. - Attribute-first contract mapping: combine
[Injectable],[Injectable<TService>], and[ServiceInjection]with clear intent. - Keyed services support: works with both native
[FromKeyedServices]and GenDI[Inject(Key = ...)]. - No runtime scanning cost: compile-time generation eliminates startup overhead from reflection-based scanning.
- AOT/trimming friendly by design: safe path for teams that need NativeAOT, without forcing this concern for every project.
dotnet add package GenDI[ServiceInjection]
public interface IMyService
{
void Execute();
}
[Injectable(ServiceLifetime.Singleton, Group = 10, Order = 1)]
public class MyService : IMyService
{
public void Execute() => Console.WriteLine("Service injected!");
}InjectableAttribute supports:
Lifetime(constructor argument, defaultTransient)Group(optional, defaultint.MaxValue)Order(optional, defaultint.MaxValue)ServiceType:[Injectable]->null(no explicit contract)[Injectable<TService>]->typeof(TService)as explicit contract (additive with[ServiceInjection])
Key(optional, defaultnull) for keyed service registration generation
Service registration emission order is:
GroupOrder- Service type name (ordinal)
using YourProject.DependencyInjection; // generated by GenDI in the consumer project namespace
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGenDIServices();
var app = builder.Build();
app.Run();Declare dependencies as required init-only properties and mark them with [Inject]. GenDI generates the activation code — no constructor needed:
[Injectable<IOrderProcessor>(ServiceLifetime.Scoped)]
public class OrderProcessor : IOrderProcessor
{
[Inject] public required IOrderRepository Repository { get; init; }
[Inject] public required IPaymentGateway Payment { get; init; }
[Inject] public required ILogger<OrderProcessor> Logger { get; init; }
public async Task ProcessAsync(Order order)
{
Logger.LogInformation("Processing order {Id}", order.Id);
await Repository.SaveAsync(order);
await Payment.ChargeAsync(order);
}
}[Inject] also supports optional Key for keyed dependency resolution:
[Inject(Key = "primary")]
public required IMyService Service { get; init; }Constructor injection is also supported and can use the native DI attribute:
public MyConsumer([FromKeyedServices("primary")] IMyService service) { }- GenDI discovers services from
[ServiceInjection]in implemented interfaces and base types. Injectable<TService>is also added to the generated registration list when provided.- If no
[ServiceInjection]is found in the inheritance/implementation chain, the concrete class is registered as its own service.
By default, generated extensions are included in coverage (no [ExcludeFromCodeCoverage]).
You can control this per assembly:
[assembly: GenDI.GenDICoveration(false)] // add [ExcludeFromCodeCoverage] to generated extensionGenDI includes linker descriptors and validation projects for trimming and NativeAOT scenarios.
dotnet publish tests/GenDI.Phase3.TrimValidation.App/GenDI.Phase3.TrimValidation.App.csproj -c Releasedotnet publish tests/GenDI.Phase3.NativeAotValidation.App/GenDI.Phase3.NativeAotValidation.App.csproj -c Release -r linux-x64<linker>
<assembly fullname="YourAssemblyName">
<type fullname="YourAssemblyName.DependencyInjection.GenDIServiceCollectionExtensions" preserve="all" />
</assembly>
</linker>GenDI now ships an English-first Docusaurus documentation website under website/, with a theme aligned to net-mediate.
cd website
npm ci
npm run startcd website
npm run buildGitHub Pages deployment is handled by .github/workflows/deploy-docs.yml.
GenDI now includes a dedicated BenchmarkDotNet project:
tests/GenDI.Benchmarks
Primary benchmark focus is startup registration cost:
- generated registration (
AddGenDIServices) - reflection-based runtime scanning
Latest published benchmark report:
docs/BENCHMARKS.md
The repository includes:
versions.propsfor centralized dynamic versioningpack.propsfor package metadata and packing defaults.github/workflows/ci-cd.ymland.github/workflows/auto-publish.ymlprepared for Sonar/NuGet flows
The repository uses local tools and Husky hooks:
dotnet-tools.jsonincludescsharpierandhusky- pre-commit runs:
dotnet csharpier format .dotnet test
For fresh clones, src/GenDI/GenDI.csproj runs a pre-restore target that executes dotnet tool restore and dotnet husky install.
| Platform / Framework | Supported |
|---|---|
| .NET 8+ | YES |
| NativeAOT | YES |
| Trimming | YES |
| Microsoft.Extensions.DependencyInjection | YES |
| Phase | Description | Status |
|---|---|---|
| 1 | InjectableAttribute - attribute-based registration |
Implemented |
| 2 | Attribute model + contract discovery + ordering | Implemented |
| 3 | Advanced NativeAOT support (ILLink.xml, trimming, AOT) | Implemented |
| 4 | Benchmarks, website/docs, and CI hardening | Implemented |
| 5 | Official NuGet publication | In Progress |
See the full plan in ROADMAP.md.
Contributions are welcome! Please read CONTRIBUTING.md before opening a pull request.
This project is licensed under the MIT License - see LICENSE.md for details.