Repository navigation
Description
Background and motivation
Source generators are a pretty handy replacement for lots of traditionally reflection-based systems, like dependency injection and IoC containers. Rather than looking up constructors and invoking them via reflection, source generator-based libraries can generate classes at build time that call specific constructors explicitly, dramatically improving the speed of service resolution at runtime. Several community libraries are already pursuing this approach for DI, including:
Unfortunately, all of these libraries are still pretty immature, with little exposure in the community, meaning there are few tutorials/articles written about them, spotty support, etc. Some of them may become more popular and outcompete the mainstays like Autofac, Ninject, Zenject, etc., but for now, their adoption is a hard sell, despite the performance benefits.
I've tried looking through the source code of the Microsoft.Extensions.DependencyInjection namespace, but it's still pretty over my head. It's unclear to me whether those implementations are already using source generators, but I'm pretty sure I read elsewhere that they're not. Seems to me that the default DI system provided by .NET should be as performant as possible, and source generators are an ideal way to achieve this.
API Proposal
This less of an API Proposal, and more of an "implementation suggestion". If the IoC containers in Microsoft.Extensions.DependencyInjection are not already using source generators, then I think that they should be!
API Usage
APIs will probably look/feel the same, but just generate a type with explicit constructor calls at build time.
Alternative Designs
The existing design. :P
Risks
Would probably involve a pretty significant rewrite of types in Microsoft.Extensions.DependencyInjection, which could take up a lot of time/resources.
Activity
jkotas commentedon Feb 26, 2023
Microsoft.Extensions.DependencyInjectiondoes not use source generators.This is the hard part. The current
Microsoft.Extensions.DependencyInjectionmodel is too dynamic, without enough static information, to make source generators work. We would need an alternative model (different APIs, custom attributes) to make source generators work well for dependency injection. The projects that you have linked demonstrate possible implementations of such alternative model.Rabadash8820 commentedon Feb 26, 2023
Makes sense. Maybe this would need a new
Microsoft.Extensions.DependencyInjection.SourceGeneratednamespace or something, to give you all the freedom to restructure the APIs. Either way, now the feature request is here; I knew it would be a while before anything like this actually materializes.In the meantime, do you (either you personally or MS as a whole) have any recommendation for a good source-generator-based DI framework? I haven't looked into any of the above super thoroughly yet...
jkotas commentedon Feb 26, 2023
@davidfowl Any recommendations for a good source-generator based DI framework?
davidfowl commentedon Feb 26, 2023
My opinion is tainted by the fact that @pakrym used to work on MEDI and built https://github.com/pakrym/jab after long discussions we had about what that model would look like. I haven't looked at the other ones in depth enough to give a good opinion on which ones to use.
Sergio0694 commentedon Feb 26, 2023
Not necessarily. For instance, I wrote a source generator which we're currently using in the Microsoft Store, which effectively just generates stubs to register services over
MS.Ex.Di. It uses a syntax very similar to what Jab does (which is cool, considering we both designed it without knowing about each other's project), ie. with attributes to define services and interfaces, and then generates the appropriate registration calls over a targetIServiceCollection. This makes it AOT and trim-safe, because services will always be registered with generated factories that will handle all dependency resolution. It also makes it pretty general, as it can work over any arbitrary service collection, as long as it implementsIServiceCollection. And it also generates useful diagnostics in case there's some registration errors at compile time. Wondering if something like this could be an approach that would make sense forMS.Ex.DI, given it's purely additive and backwards compatible? Happy to ask if I can open source the code in case it could be useful 🙂Rabadash8820 commentedon Feb 27, 2023
Yeah something like what @Sergio0694 is describing sounds awesome. For now I'll definitely take a closer look at jab, sounds like it's got some strong talent behind it. Mainly I'm just looking for something that will be maintained for the foreseeable future, and the MS extensions are pretty much the best in that regard. 👌
pakrym commentedon Mar 2, 2023
Source generation can't be a goal in itself; it has to solve some problems like startup performance, trimability, or runtime performance.
It looks like .NET team mostly figured out trimming.
The runtime performance of MEDI is better than source-generated DI implementations on .NET Core because it can "rewrite" resolution code at runtime and do tricks like embedding direct references to singleton objects into transient service factory implementations and skipping extra checks that source-generated implementations can't avoid.
Source-generated containers have better startup performance because they can compute the service graph and run checks during the compilation. Unfortunately, this doesn't fit well into Microsoft.Extension.* ecosystem where the set of dependency injection services is controlled at runtime as @jkotas pointed out above.
Knowing the service graph requires running the code, and every time you run the code, you can get a different graph. I toyed with the idea of a double-tap source-generated container where on the first app run it dumps the service graph, and on the second compilation, the graph gets turned into a source-gen container. Unfortunately, there is no guarantee that the service graph doesn't change between two invocations so the compilation result might instantly be wrong.
Another limitation that source-generated containers have is that they can only operate only on types accessible to the compiled assembly. MEDI doesn't have this limitation, and ASP.NET heavily relies on the ability of service implementations to stay internal.
In short, my opinion is that source-generated containers might be useful in short-lived applications where the entire service graph is controlled by the author. Unfortunately in .NET ecosystem right now, this means not using any Microsoft.Extensions or ASP.NET Core libraries.
For things really to change and enable true source-gen DI, Microsoft.Extensions.* and Microsoft.AspNetCore.* libraries will have to provide static (e.g. attribute-based) descriptions of their service graphs and public implementations of their services. In that case, it will be possible for a source-generated container to implement a IServiceProvider for ASP.NET Core application. Having said that, I'm not sure whether this is realistic considering how embedded the idea of using dependency injection as a configuration primitive is in the .NET ecosystem.
eerhardt commentedon Apr 25, 2023
For completeness, I'm linking my notes on this topic here. They are mostly just duplicates of what is said above.
I believe this issue is a duplicate #44432.
Rabadash8820 commentedon Apr 25, 2023
Thanks for the discussion, all. I agree that this issue is a duplicate, so feel free to close.