Pure DI for .NET
Key features
Pure.DI is not a framework or library, but a source code generator. It generates a partial class to create object graphs in the Pure DI paradigm. To make this object graph accurate, it uses a set of hints that are checked at compile time. Since all the work is done at compile time, you only have effective code ready to use at run time. The generated code is independent of .NET library calls or reflection and is efficient in terms of performance and memory consumption because, like normal code, it is subject to all optimizations and integrates seamlessly into any application - without using delegates, extra method calls, boxing, type conversions, etc.
- DI without any IoC/DI containers, frameworks, dependencies and therefore without performance impact or side effects.
Pure.DI is actually a .NET code generator. It generates simple code as well as if you were doing it yourself: de facto just a bunch of nested constructors` calls. And you can see this code at any time.
- A predictable and verifiable dependency graph is built and verified on the fly while you write the code.
All the logic for analyzing the graph of objects, constructors, methods happens at compile time. Thus, the Pure.DI tool notifies the developer about missing or circular dependency, for cases when some dependencies are not suitable for injection, etc., at compile-time. Developers have no chance of getting a program that crashes at runtime due to these errors. All this magic happens simultaneously as the code is written, this way, you have instant feedback between the fact that you made some changes to your code and your code was already checked and ready for use.
- Does not add any dependencies to other assemblies.
Using a pure DI approach, you don't add runtime dependencies to your assemblies.
- Highest performance, including compiler and JIT optimizations.
All generated code runs as fast as your own, in pure DI style, including compile-time and run-time optimizations. As mentioned above, graph analysis doing at compile-time, but at run-time, there are just a bunch of nested constructors, and that's it.
- It works everywhere.
Since a pure DI approach does not use any dependencies or the .NET reflection at runtime, it does not prevent your code from working as expected on any platform: Full .NET Framework 2.0+, .NET Core, .NET, UWP/XBOX, .NET IoT, Xamarin, Native AOT, etc.
- Ease of use.
The Pure.DI API is very similar to the API of most IoC/DI libraries. And it was a deliberate decision: the main reason is that programmers do not need to learn a new API.
- Ultra-fine tuning of generic types.
Pure.DI offers special type markers instead of using open generic types. This allows you to more accurately build the object graph and take full advantage of generic types.
- Supports basic .NET BCL types out of the box.
Pure.DI already supports many of BCL types like
Array
,IEnumerable<T>
,IList<T>
,ISet<T>
,Func<T>
,ThreadLocal
,Task<T>
,MemoryPool<T>
,ArrayPool<T>
,ReadOnlyMemory<T>
,Memory<T>
,ReadOnlySpan<T>
,Span<T>
,IComparer<T>
,IEqualityComparer<T>
and etc. without any extra effort. - Good for creating libraries or frameworks and where resource consumption is particularly critical.
High performance, zero memory consumption/preparation overhead and no dependencies make it an ideal assistant for building libraries and frameworks.
Schrödinger's cat shows how it works
The reality is that
Let's create an abstraction
interface IBox<out T> { T Content { get; } }
interface ICat { State State { get; } }
enum State { Alive, Dead }
Here's our implementation
class CardboardBox<T> : IBox<T>
{
public CardboardBox(T content) => Content = content;
public T Content { get; }
}
class ShroedingersCat : ICat
{
// Represents the superposition of the states
private readonly Lazy<State> _superposition;
public ShroedingersCat(Lazy<State> superposition) => _superposition = superposition;
// Decoherence of the superposition at the time of observation via an irreversible process
public State State => _superposition.Value;
public override string ToString() => $"{State} cat";
}
It is important to note that our abstraction and implementation knows nothing about DI magic or any frameworks. Also, please note that an instance of type Lazy<> has been used here only as an example. However, using this type with non-trivial logic as a dependency is not recommended, so consider replacing it with some simple abstract type.
Let's glue it all together
Add a package reference to
Package Manager
Install-Package Pure.DI
.NET CLI
dotnet add package Pure.DI
Bind abstractions to their implementations or factories, define lifetimes and other options in a class like the following:
partial class Composition
{
// In fact, this code is never run, and the method can have any name or be a constructor, for example,
// and can be in any part of the compiled code because this is just a hint to set up an object graph.
// Here the setup is part of the generated class, just as an example.
private static void Setup() => DI.Setup(nameof(Composition))
// Models a random subatomic event that may or may not occur
.Bind<Random>().As(Singleton).To<Random>()
// Represents a quantum superposition of 2 states: Alive or Dead
.Bind<State>().To(ctx =>
{
ctx.Inject<Random>(out var random);
return (State)random.Next(2);
})
// Represents schrodinger's cat
.Bind<ICat>().To<ShroedingersCat>()
// Represents a cardboard box with any content
.Bind<IBox<TT>>().To<CardboardBox<TT>>()
// Composition Root
.Root<Program>("Root");
}
The code above is just a chain of hints to define the dependency graph used to create a Composition class with a Root property that creates the Program composition root below. It doesn't really make sense to run this code because it doesn't do anything at runtime. So it can be placed anywhere in the class (in methods, constructors or properties) and preferably where it will not be called. Its purpose is only to check the dependency syntax and help build the dependency graph at compile time to create the Composition class. The first argument to the Setup method specifies the name of the class to be generated.
Time to open boxes!
class Program
{
// Composition Root, a single place in an application
// where the composition of the object graphs for an application take place
public static void Main() => new Composition().Root.Run();
private readonly IBox<ICat> _box;
internal Program(IBox<ICat> box) => _box = box;
private void Run() => Console.WriteLine(_box);
}
Root is a Composition Root here, a single place in an application where the composition of the object graphs for an application takes place. Each instance is resolved by a strongly-typed block of statements like the operator new, which are compiling with all optimizations with minimal impact on performance or memory consumption. The generated Composition class contains a Root property that allows you to resolve an instance of the Program type.
Root property
public Sample.Program Root
{
get
{
Func<State> stateFunc = new Func<State>(() =>
{
if (_randomSingleton == null)
{
lock (_lockObject)
{
if (_randomSingleton == null)
{
_randomSingleton = new Random();
}
}
}
return (State)_randomSingleton.Next(2);
});
return new Program(
new CardboardBox<ICat>(
new ShroedingersCat(
new Lazy<Sample.State>(
stateFunc))));
}
}
You can find a complete analogue of this application with top level statements here. For a top level statements application the name of generated composer is "Composer" by default if it was not override in the Setup call.
Pure.DI works the same as calling a set of nested constructors, but allows dependency injection. And that's a reason to take full advantage of Dependency Injection everywhere, every time, without any compromise!
Just try!
Download the example project code
git clone https://github.com/DevTeam/Pure.DI.Example.git
And run it from solution root folder
cd ./Pure.DI.Example
dotnet run
Examples
Basics
- Composition root
- Resolve methods
- Factory
- Injection
- Generics
- Arguments
- Tags
- Auto-bindings
- Child composition
- Multi-contract bindings
- Field injection
- Method injection
- Property injection
- Complex generics
- Partial class
- Dependent compositions
- Required properties or fields
Lifetimes
Attributes
- Constructor ordinal attribute
- Member ordinal attribute
- Tag attribute
- Type attribute
- Custom attributes
Base Class Library
- Func
- Enumerable
- Array
- Lazy
- Span and ReadOnlySpan
- Tuple
- Service provider
- Service collection
- Func with arguments
- Overriding the BCL binding
Interception
Hints
- Resolve hint
- ThreadSafe hint
- OnDependencyInjection hint
- OnCannotResolve hint
- OnNewInstance hint
- ToString hint
Applications
Generated Code
Each generated class, hereinafter referred to as composition, needs to be configured. It starts with the Setup(...)
method:
DI.Setup("Composition")
.Bind<IDependency>().To<Dependency>()
.Bind<IService>().To<Service>()
.Root<IService>("Root");
The following class will be generated
partial class Composition
{
public Composition() { }
internal Composition(Composition parent) { }
public IService Root
{
get
{
return new Service(new Dependency());
}
}
public T Resolve<T>() { ... }
public T Resolve<T>(object? tag) { ... }
public object Resolve(System.Type type) { ... }
public object Resolve(System.Type type, object? tag) { ... }
}
Setup arguments
The first parameter is used to specify the name of the composition class. All setups with the same name will be combined to create one composition class. In addition, this name may contain a namespace, for example for Sample.Composition
the composition class is generated:
namespace Sample
{
partial class Composition
{
...
}
}
The second optional parameter can have several values to determine the kind of composition.
CompositionKind.Public
This is the default value. If this value is specified, a composition class will be created.
CompositionKind.Internal
If this value is specified, the class will not be generated, but this setup can be used for others as a base. For example:
DI.Setup("BaseComposition", CompositionKind.Internal)
.Bind<IDependency>().To<Dependency>();
DI.Setup("Composition").DependsOn("BaseComposition")
.Bind<IService>().To<Service>();
When the composition CompositionKind.Public flag is set in the composition setup, it can also be the base composition for others like in the example above.
CompositionKind.Global
If this value is specified, no composition class will be created, but this setup is the base for all setups in the current project, and DependsOn(...)
is not required.
Constructors
Default constructor
Everything is quite banal, this constructor simply initializes the internal state.
Argument constructor
It replaces the default constructor and is only created if at least one argument is provided. For example:
DI.Setup("Composition")
.Arg<string>("name")
.Arg<int>("id")
...
In this case, the argument constructor looks like this:
public Composition(string name, int id) { ... }
and default constructor is missing.
Child constructor
This constructor is always available and is used to create a child composition based on the parent composition:
var parentComposition = new Composition();
var childComposition = new Composition(parentComposition);
The child composition inherits the state of the parent composition in the form of arguments and singleton objects. States are copied, and compositions are completely independent, except when calling the Dispose() method on the parent container before disposing of the child container, because the child container can use singleton objects created before it was created.
Properties
To be able to quickly and conveniently create an object graph, a set of properties is generated. These properties are called compositions roots here. The type of the property is the type of a root object created by the composition. Accordingly, each access to the property leads to the creation of a composition with the root element of this type.
Public Composition Roots
To be able to use a specific composition root, that root must be explicitly defined by the Root method with a specific name and type:
DI.Setup("Composition")
.Bind<IService>().To<Service>()
.Root<IService>("MyService");
In this case, the property for type IService will be named MyService and will be available for direct use. The result of its use will be the creation of a composition of objects with a root of type IService:
public IService MyService
{
get
{
...
return new Service(...);
}
}
This is recommended way to create a composition root. A composition class can contain any number of roots.
Private Composition Roots
When the root name is empty, a private composition root is created. This root is used in these Resolve methods in the same way as public roots. For example:
DI.Setup("Composition")
.Bind<IService>().To<Service>()
.Root<IService>();
private IService Root1ABB3D0
{
get { ... }
}
These properties have a random name and a private accessor and cannot be used directly from code. Don't try to use them. Private composition roots can be resolved by the Resolve methods.
Methods
Resolve
By default a set of four Resolve methods are generated:
public T Resolve<T>() { ... }
public T Resolve<T>(object? tag) { ... }
public object Resolve(Type type) { ... }
public object Resolve(Type type, object? tag) { ... }
These methods can resolve public composition roots as well as private roots and are useful when using the Service Locator approach when the code resolves composition roots in place:
var composition = new Composition();
composition.Resolve<IService>();
This is not recommended way to create composition roots. To control the generation of these methods, see the Resolve hint.
Dispose
Provides a mechanism for releasing unmanaged resources. This method is only generated if the composition contains at least one singleton instance that implements the IDisposable interface. To dispose of all created singleton objects, call the composition Dispose()
method:
using(var composition = new Composition())
{
...
}
Setup hints
Setup hints
Hints are used to fine-tune code generation. Setup hints can be used as in the following example:
DI.Setup("Composition")
.Hint(Hint.Resolve, "Off")
.Hint(Hint.ThreadSafe, "Off")
.Hint(Hint.ToString, "On")
...
In addition, setup hints can be comments before the Setup method in the form hint = value
, for example:
// Resolve = Off
// ThreadSafe = Off
DI.Setup("Composition")
.Hint(Hint.ToString, "On")
...
Resolve Hint
Determines whether to generate Resolve methods. By default a set of four Resolve methods are generated. Set this hint to Off to disable the generation of resolve methods. This will reduce class composition generation time and no private composition roots will be generated in this case. The composition will be tiny and will only have public roots. When the Resolve hint is disabled, only the public root properties are available, so be sure to define them explicitly with the Root<T>(...)
method.
OnNewInstance Hint
Determines whether to generate partial OnNewInstance method. This partial method is not generated by default. This can be useful, for example, for logging:
internal partial class Composition
{
partial void OnNewInstance<T>(ref T value, object? tag, object lifetime)
{
Console.WriteLine($"'{typeof(T)}'('{tag}') created.");
}
}
You can also replace the created instance of type T
, where T
is actually type of created instance. To minimize the performance penalty when calling OnNewInstance, use the three related hints below.
OnNewInstanceImplementationTypeNameRegularExpression Hint
It is a regular expression to filter by the instance type name. This hint is useful when OnNewInstance is in the On state and you want to limit the set of types for which the method OnNewInstance will be called.
OnNewInstanceTagRegularExpression Hint
It is a regular expression to filter by the tag. This hint is useful also when OnNewInstance is in the On state and you want to limit the set of tag for which the method OnNewInstance will be called.
OnNewInstanceLifetimeRegularExpression Hint
It is a regular expression to filter by the lifetime. This hint is useful also when OnNewInstance is in the On state and you want to limit the set of lifetime for which the method OnNewInstance will be called.
OnDependencyInjection Hint
Determines whether to generate partial OnDependencyInjection method to control of dependency injection. This partial method is not generated by default. It cannot have an empty body due to the return value. It must be overridden when generated. This can be useful, for example, for interception.
// OnDependencyInjection = On
// OnDependencyInjectionContractTypeNameRegularExpression = ICalculator[\d]{1}
// OnDependencyInjectionTagRegularExpression = Abc
DI.Setup("Composition")
...
To minimize the performance penalty when calling OnDependencyInjection, use the three related hints below.
OnDependencyInjectionImplementationTypeNameRegularExpression Hint
It is a regular expression to filter by the instance type name. This hint is useful when OnDependencyInjection is in the On state and you want to limit the set of types for which the method OnDependencyInjection will be called.
OnDependencyInjectionContractTypeNameRegularExpression Hint
It is a regular expression to filter by the resolving type name. This hint is useful also when OnDependencyInjection is in the On state and you want to limit the set of resolving types for which the method OnDependencyInjection will be called.
OnDependencyInjectionTagRegularExpression Hint
It is a regular expression to filter by the tag. This hint is useful also when OnDependencyInjection is in the On state and you want to limit the set of tag for which the method OnDependencyInjection will be called.
OnDependencyInjectionLifetimeRegularExpression Hint
It is a regular expression to filter by the lifetime. This hint is useful also when OnDependencyInjection is in the On state and you want to limit the set of lifetime for which the method OnDependencyInjection will be called.
OnCannotResolve Hint
Determines whether to generate a partial OnCannotResolve<T>(...)
method to handle a scenario where an instance which cannot be resolved. This partial method is not generated by default. It cannot have an empty body due to the return value. It must be overridden on creation.
// OnCannotResolve = On
// OnCannotResolveContractTypeNameRegularExpression = string|DateTime
// OnDependencyInjectionTagRegularExpression = null
DI.Setup("Composition")
...
To avoid missing bindings by mistake, use the two related hints below.
OnNewRoot Hint
Determines whether to generate a static partial OnNewRoot<TContract, T>(...)
method to handle the new composition root registration event.
// OnNewRoot = On
DI.Setup("Composition")
...
OnCannotResolveContractTypeNameRegularExpression Hint
It is a regular expression to filter by the resolving type name. This hint is useful also when OnCannotResolve is in the On state and you want to limit the set of resolving types for which the method OnCannotResolve will be called.
OnCannotResolveTagRegularExpression Hint
It is a regular expression to filter by the tag. This hint is useful also when OnCannotResolve is in the On state and you want to limit the set of tag for which the method OnCannotResolve will be called.
OnCannotResolveLifetimeRegularExpression Hint
It is a regular expression to filter by the lifetime. This hint is useful also when OnCannotResolve is in the On state and you want to limit the set of lifetime for which the method OnCannotResolve will be called.
ToString Hint
Determine if the ToString() method should be generated. This method provides a text-based class diagram in the format mermaid. To see this diagram, just call the ToString method and copy the text to this site.
// ToString = On
DI.Setup("Composition")
.Bind<IService>().To<Service>()
.Root<IService>("MyService");
var composition = new Composition();
string classDiagram = composition.ToString();
ThreadSafe Hint
This hint determines whether the composition of objects will be created in a thread-safe manner. The default value of this hint is On. It is good practice not to use threads when creating an object graph, in which case the hint can be disabled, resulting in a slight performance gain.
// ThreadSafe = Off
DI.Setup("Composition")
.Bind<IService>().To<Service>()
.Root<IService>("MyService");
ResolveMethodModifiers Hint
Overrides modifiers of the method public T Resolve<T>()
.
ResolveMethodName Hint
Overrides name of the method public T Resolve<T>()
.
ResolveByTagMethodModifiers Hint
Overrides modifiers of the method public T Resolve<T>(object? tag)
.
ResolveByTagMethodName Hint
Overrides name of the method public T Resolve<T>(object? tag)
.
ObjectResolveMethodModifiers Hint
Overrides modifiers of the method public object Resolve(Type type)
.
ObjectResolveMethodName Hint
Overrides name of the method public object Resolve(Type type)
.
ObjectResolveByTagMethodModifiers Hint
Overrides modifiers of the method public object Resolve(Type type, object? tag)
.
ObjectResolveByTagMethodName Hint
Overrides name of the method public object Resolve(Type type, object? tag)
.
DisposeMethodModifiers Hint
Overrides modifiers of the method public void Dispose()
.
FormatCode Hint
Specifies whether the generated code should be formatted. This option consumes a lot of CPU resources. This hint can be useful for examining the generated code or for giving presentations, for example.
Development environment requirements
Supported frameworks
- .NET and .NET Core 1.0+
- .NET Standard 1.0+
- Native AOT
- .NET Framework 2.0+
- UWP/XBOX
- .NET IoT
- Xamarin
- WPF
- .NET Multi-platform App UI (MAUI)
Project template
Install the DI template Pure.DI.Templates
dotnet new -i Pure.DI.Templates
Create a "Sample" console application from the template di
dotnet new di -o ./Sample
And run it
dotnet run --project Sample
Please see this page for more details about the template.
Troubleshooting
Generated files
You can set build properties to save the generated file and control where the generated files are stored. In a project file, add the element to a , and set its value to true. Build your project again. Now, the generated files are created under obj/Debug/netX.X/generated/Pure.DI/Pure.DI.SourceGenerator. The components of the path map to the build configuration, target framework, source generator project name, and fully qualified type name of the generator. You can choose a more convenient output folder by adding the element to the application's project file. For example:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
Log files
You can set build properties to save the log file. In the project file, add a element to the and set the path to the log directory, and add the related element <CompilerVisibleProperty Include="PureDILogFile" />
to the to make this property visible in the source generator. To change the log level, specify the same with the PureDISeverity property, as in the example below:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PureDILogFile>.logs\Pure.DI.log</PureDILogFile>
<PureDISeverity>Info</PureDISeverity>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="PureDILogFile" />
<CompilerVisibleProperty Include="PureDISeverity" />
</ItemGroup>
</Project>
The PureDISeverity property has several options available:
Severity | Description |
---|---|
Hidden | Debug information. |
Info | Information that does not indicate a problem (i.e. not prescriptive). |
Warning | Something suspicious, but allowed. This is the default value. |
Error | Something not allowed by the rules of the language or other authority. |
Benchmarks
Transient
Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
---|---|---|---|---|---|---|
'Hand Coded' | 0.0128 ns | 0.0166 ns | 0.0238 ns | 0.0000 ns | ||
'Pure.DI composition root' | 3.9792 ns | 0.1370 ns | 0.3998 ns | 3.8511 ns | ||
Pure.DI | 4.8851 ns | 0.1555 ns | 0.2375 ns | 4.8104 ns | ||
'Pure.DI non-generic' | 7.9274 ns | 0.2154 ns | 0.5364 ns | 7.7121 ns | ||
LightInject | 11.0228 ns | 0.2509 ns | 0.6249 ns | 10.8849 ns | ||
DryIoc | 20.5335 ns | 0.4306 ns | 0.6954 ns | 20.4471 ns | ||
SimpleInjector | 22.7638 ns | 0.4747 ns | 0.8799 ns | 22.6135 ns | ||
MicrosoftDependencyInjection | 23.4549 ns | 0.4783 ns | 0.9328 ns | 23.4470 ns | ||
Autofac | 8,924.6020 ns | 178.0725 ns | 277.2375 ns | 8,920.4597 ns |
Singleton
Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
---|---|---|---|---|---|---|
'Hand Coded' | 0.0175 ns | 0.0208 ns | 0.0223 ns | 0.0025 ns | ||
'Pure.DI composition root' | 4.4046 ns | 0.1675 ns | 0.4913 ns | 4.2524 ns | ||
Pure.DI | 5.4675 ns | 0.1651 ns | 0.4867 ns | 5.2425 ns | ||
'Pure.DI non-generic' | 7.8375 ns | 0.2136 ns | 0.3262 ns | 7.7274 ns | ||
DryIoc | 19.3453 ns | 0.4133 ns | 0.7964 ns | 19.0213 ns | ||
SimpleInjector | 21.9625 ns | 0.4665 ns | 0.5900 ns | 21.9175 ns | ||
MicrosoftDependencyInjection | 23.4351 ns | 0.3397 ns | 0.2837 ns | 23.3851 ns | ||
LightInject | 31.4078 ns | 0.6519 ns | 1.0150 ns | 30.9192 ns | ||
Autofac | 6,292.0519 ns | 123.0989 ns | 184.2486 ns | 6,275.1659 ns |
Func
Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
---|---|---|---|---|---|---|
'Hand Coded' | 79.40 ns | 2.333 ns | 6.842 ns | 76.23 ns | 1.00 | 0.00 |
'Pure.DI composition root' | 83.99 ns | 2.211 ns | 6.483 ns | 80.88 ns | 1.07 | 0.12 |
Pure.DI | 84.28 ns | 2.507 ns | 7.314 ns | 81.52 ns | 1.07 | 0.11 |
'Pure.DI non-generic' | 91.09 ns | 3.432 ns | 10.011 ns | 88.74 ns | 1.16 | 0.17 |
DryIoc | 109.17 ns | 3.266 ns | 9.579 ns | 106.92 ns | 1.38 | 0.16 |
LightInject | 441.57 ns | 8.785 ns | 21.877 ns | 431.23 ns | 5.56 | 0.58 |
Autofac | 8,645.17 ns | 170.212 ns | 319.699 ns | 8,604.62 ns | 109.90 | 10.55 |
Array
Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
---|---|---|---|---|---|---|
'Hand Coded' | 79.99 ns | 2.698 ns | 7.954 ns | 76.70 ns | 1.00 | 0.00 |
'Pure.DI composition root' | 88.79 ns | 2.717 ns | 7.884 ns | 86.02 ns | 1.12 | 0.15 |
Pure.DI | 92.69 ns | 3.242 ns | 9.509 ns | 88.42 ns | 1.17 | 0.15 |
'Pure.DI non-generic' | 93.28 ns | 2.674 ns | 7.884 ns | 90.48 ns | 1.18 | 0.14 |
LightInject | 99.37 ns | 3.547 ns | 10.347 ns | 97.70 ns | 1.25 | 0.18 |
DryIoc | 108.40 ns | 3.156 ns | 9.257 ns | 104.45 ns | 1.37 | 0.17 |
Autofac | 10,368.08 ns | 203.428 ns | 429.098 ns | 10,342.65 ns | 130.12 | 15.68 |
Enum
Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
---|---|---|---|---|---|---|
'Hand Coded' | 188.5 ns | 3.90 ns | 11.51 ns | 183.6 ns | 1.00 | 0.00 |
Pure.DI | 188.7 ns | 3.82 ns | 10.12 ns | 184.9 ns | 1.00 | 0.08 |
'Pure.DI composition root' | 199.6 ns | 4.25 ns | 12.52 ns | 195.1 ns | 1.06 | 0.09 |
'Pure.DI non-generic' | 207.1 ns | 4.17 ns | 11.06 ns | 203.2 ns | 1.10 | 0.07 |
LightInject | 213.0 ns | 4.65 ns | 13.71 ns | 206.0 ns | 1.13 | 0.10 |
DryIoc | 222.9 ns | 4.56 ns | 13.38 ns | 217.4 ns | 1.19 | 0.11 |
MicrosoftDependencyInjection | 234.3 ns | 5.20 ns | 15.17 ns | 228.7 ns | 1.25 | 0.10 |
Autofac | 9,865.7 ns | 196.14 ns | 240.87 ns | 9,755.9 ns | 51.19 | 4.06 |
Benchmarks environment
BenchmarkDotNet v0.13.6, Windows 10 (10.0.19045.3208/22H2/2022Update)
Intel Core i7-10850H CPU 2.70GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 7.0.304
[Host] : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2