π§© Introduction to GenDI
GenDI is an attribute-first dependency injection source generator for .NET. It generates DI registrations and activation code at compile time β no reflection, no runtime scanning, no boilerplate.
π€ Stop writing constructor boilerplateβ
Every .NET developer knows the pain: a service gains one more dependency, so you update the constructor signature, add another private field, and repeat the same assignment yet again. As a codebase grows, constructors become maintenance burdens rather than meaningful code.
GenDI eliminates this ceremony entirely with property injection:
// β Traditional constructor injection β repetitive, noisy, hard to extend
public class ReportService
{
private readonly IReportRepository _repo;
private readonly IEmailService _email;
private readonly IStorageService _storage;
private readonly ILogger<ReportService> _logger;
public ReportService(
IReportRepository repo,
IEmailService email,
IStorageService storage,
ILogger<ReportService> logger)
{
_repo = repo;
_email = email;
_storage = storage;
_logger = logger;
}
}
// β
GenDI property injection β clean, declarative, self-documenting
[Injectable<IReportService>(ServiceLifetime.Scoped)]
public class ReportService : IReportService
{
[Inject] public required IReportRepository Repo { get; init; }
[Inject] public required IEmailService Email { get; init; }
[Inject] public required IStorageService Storage { get; init; }
[Inject] public required ILogger<ReportService> Logger { get; init; }
}
The required keyword guarantees that every dependency is provided β the compiler enforces it. GenDI generates the wiring code so you never touch it.
π Why property injection winsβ
| Constructor injection | GenDI property injection | |
|---|---|---|
| β Adding a dependency | Edit ctor signature + field + assignment | Add one [Inject] property |
| π Reading the class | Ctor signature + private fields | Properties listed at a glance |
| π§ͺ Unit testing | Build a mock for every ctor param | Assign only the props you need |
| π Refactoring | Risk of parameter order mistakes | Properties are named β no position bugs |
| βοΈ Generated code | None | Explicit, readable, debuggable |
β¨ Key features and practical valueβ
- π― Property injection as first-class citizen:
[Inject]onrequiredinit-only properties β dependencies read like documentation, not plumbing. - π« Zero boilerplate registration: one
[Injectable]attribute replaces manualAddScoped<>()calls in startup files. - π Compile-time safety: the C# compiler enforces that every
required[Inject]property is assigned in the generated initializer β you cannot accidentally omit a dependency. Note: unregistered services still surface as runtime container exceptions, just like standard DI. - π Readable generated flow: activation uses explicit
new+GetRequiredService<T>(), easy to inspect and debug. - π Predictable behavior: deterministic ordering with
GroupandOrderavoids ambiguous pipeline composition. - π Modern DI scenarios: keyed registrations and keyed resolution through both Microsoft DI and GenDI attributes.
- β‘ No startup overhead: compile-time generation eliminates reflection-based scanning costs.
- π Future-proof deployment: NativeAOT and trimming support when you need it β no friction for traditional deployments.
π‘ Why GenDI existsβ
Traditional runtime scanning is practical, but adds startup cost and can break with aggressive trimming or NativeAOT. GenDI shifts all of this to compile-time:
- Registration mapping is generated from attributes.
- Property and constructor injection are generated as strongly typed C# code.
- Microsoft DI remains the runtime container β GenDI is purely additive.
π Feature summaryβ
[Injectable]and[Injectable<TService>]to mark concrete services[ServiceInjection]to mark interfaces/abstract contracts[Inject]for init-only property injection (get; init;) β the idiomatic GenDI way[assembly: GenDICoveration(...)]to control generated extension coverage behavior- Ordering by
Group, thenOrder, then service type name (ordinal)
βοΈ Typical generation outputβ
GenDI generates an extension method in the consumer assembly namespace:
// <AssemblyName>.DependencyInjection
services.AddGenDIServices();
Each registration uses generated new expressions and GetRequiredService<T>(), keeping activation explicit and analyzer-friendly.
πΊ οΈ Documentation mapβ
- π¦ Getting Started: installation and first setup
- π Core Concepts: attributes, contracts, registration strategy
- π¬ Advanced: NativeAOT/trimming validation, benchmarks and test strategy
- π Community: contribution, roadmap and sponsorship