Dependency Injection for Native AOT

Inject some objects into your code. Photo by FRANK MERIÑO.

Dependency injection in recent years is a very important topic, and Microsoft.Extensions.DependencyInjection package is second after Newtonsoft.Json in popularity. So what state of DI in Native AOT?

DI working in Native AOT

Obviously it is working because it only relies on reflection and M.E.DI (shorthand for Microsoft.Extensions.DependencyInjection) are properly annotated for trimming. Autofac working, DryIoc working, Grace working too. LightInject guess what? Working too. What about StrongInject? Working! Other DI containers should work too.

So why bother then and try to write an article about it? Because in addition to regular Native AOT there is a variant of it which completely disables reflection and can make your application even slimmer. So called reflection-free mode. Which is not supported by MS by the way. So please do not go to the runtime repo and do not file issues there regarding this usage scenario. Most likely they would be redirected to this issue. Only difference between supported and not supported is whether you have <IlcDisableReflection>true</IlcDisableReflection> in your project file, or not.

All aforementioned DI containers do not work in the reflection-free mode.

Let me explain what’s not working in so-called reflection-free mode so you can guess why DI can rely on these features.

Maybe you want to create an instance of type using a constructor?

var constructors = typeof(Test).GetConstructors();
var instance1 = constructors.First().Invoke(new Type[0]);

Or maybe you want to use non-generic Activator.CreateInstance ?

var instance2 = Activator.CreateInstance(typeof(Test));

All of that works in regular Native AOT, because it has reflection. But snippets above would not work in reflection-free mode, since you need to have some form of reflection metadata kept around. So what to do if you want to make your app leaner?

Reflection-free problems

You can think about DI as a glorified map between types and objects with some object instantiation sprinkled in. Types as keys are fine, but what’s in the keys is a potential problem.

In the M.E.DI you have following common ways to define your services:

  • Provide type implementation using AddXXX<TService, TImplementation>()
  • Provide factory method for service AddXXX<TService>(Func<IServiceProvider, object>
  • Provide object instance using AddXXX<TService>
  • Provide open-generic service declaration by declaring service and implementation using TService<X> and TImplementation<X> generic types.
  • IEnumerable usage of service, does not registered implicitly, but services of this kind created on request.

Providing factory methods and instances would work for the DI even in reflection-free, since that’s your code responsible for creation and I bet you do everything possible to make your code work in reflection-free. Other ways not so much. For example when you provide type implementation, then DI container somehow should create a service instance for you, and without completely disabled reflection it is not possible to do. Same for open generics.

So what to do?

My answer to this is source generator. I know, there at least 2 additional source generators which provide DI which works in reflection-free mode — Jab and Pure DI. What’s the difference? For me the only difference is that you can take existing code and make it run without rewriting for new DI. So instead of reinventing the wheel, I think that existing solutions should be improved to make it reflection-free friendly. So while writing this I realize that I can provide a solution for Autofac as well. Hopefully I have enough vigor to finish that project.

So my idea has two sides. First side is to provide a way to augment existing code in such a way that your code instantly becomes reflection-free friendly. Second side is to create a statically discovered container and make it as fast as handwritten code.

So I manage to make it both ways somewhat. I cannot say that it’s a success, but I think that’s interesting enough to share with the world.

Consider this sample code

I will provide 2 factory methods for each service registration using type. And the most important trick, I provide custom AddScoped implementation within the namespace of the class where AddScoped is used, so Rolsyn picks up my implementation instead of the extension method which comes from M.E.DI. Hopefully AddXXX methods are extension methods, so I can do my dirty tricks using source generators.

To illustrate generated code:

After the source generator is added to the project, all calls to AddXXX methods are reflection-free compatible. Limitations of that approach is that each assembly should have reference to this source generator. Otherwise you may have parts of your container which do not play well with reflection-free mode.

Second approach is more interesting, but more limiting. Simply by changing a call from BuildServiceProvider to BuildServiceProviderAot developer receives a container which has all registered services in the assembly, and can resolve it. Building of such containers happens extremely fast, as well as service resolution.

You can take a look at numbers, to compare.

Legend

MEDI — regular Microsoft Extensions Dependency Injection

MEDI_Augmented — MEDI container with source generator replacing calls to AddXXX

MEDI_Aot — Source generated container with services statically resolved at compile time. Similar to Jab and Pure.DI

Jab — is Jab :)

PureDI — is Pure.DI

I would not bet my money on the correctness of these numbers, and you can take a look at source code. At least I use the same approach as Jab.

Grain of salt

Open generics still does not work. So registrations like AddScoped<ILogger<>, Logger<>> cannot be used. Reason for that is that I do not see all registrations of ILogger<MyClass> during the call to AddScoped for example. I may scan for ILogger<MyClass> usages in the other services analyzed assembly, but that’s not enough to prove that I support open-generics. There is more work needed in that direction.

That affects the ability of this library to augment M.E.Logging and MediatR for example. And usage of M.E.Logging is huge in the .NET ecosystem.

Summary

̶C̶u̶r̶r̶e̶n̶t̶l̶y̶ ̶o̶n̶l̶y̶ ̶M̶E̶D̶I̶ ̶s̶u̶p̶p̶o̶r̶t̶s̶ ̶r̶e̶f̶l̶e̶c̶t̶i̶o̶n̶-̶f̶r̶e̶e̶ ̶m̶o̶d̶e̶. Right now only MEDI and other source generator based libraries like Jab, Pure.DI and StrongInject working with reflection-free mode, but that very likely other DI libraries can catch up if they really want this. I hope they do. Reflection-free mode is a good way to find unexpected usage of reflection.

My approach shows that MEDI out of the box supports reflection-free mode, if you use only factory or instance registrations. So you may not even need this source generator if you are willing to sacrifice some developer experience and do not use handy extension methods which breaks in reflection-free mode.

Also would be good if somebody gives practical feedback on the approach, and if there is interest in pursuing reflection-free mode even more.

Source code

Nuget

Sample

Very small sample, so you can just compile and try it yourself.

--

--

--

Math lover, lost in the woods of software development

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

C# Using Alias Directive

AWS End to End Project using Terraform

International Students’ Day: Learn Cloud

How I changed a “running out of storage” problem to a “now I don’t have to worry” solution.

Move your first application to IBM Cloud private in three steps

Alibaba Cloud’s “All-in-Cloud” Future: 8 Key Takeaways from the Alibaba Cloud Singapore Summit

How to Read a Remote IP Address Behind Ingress and Load Balancer in OKE

Deploy Serverless Machine Learning Models to AWS Lambda

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Andrey Kurdyumov

Andrey Kurdyumov

Math lover, lost in the woods of software development

More from Medium

Custom code analyzer to detect usage of AllowAnonymous attribute in C#

Dot Net 6 and How It Impacts Various Operating Systems?

gRPC in .NET 6

Things You Should Know About .NET 5