Skip to content

Latest commit

 

History

History
1167 lines (872 loc) · 61 KB

README.md

File metadata and controls

1167 lines (872 loc) · 61 KB

Pure DI for .NET

NuGet License Build Performance Build GitHub Build

Supports .NET starting with .NET Framework 2.0, released 2005-10-27, and all newer versions.

Usage requirements

  • .NET SDK 6.0.4 or later is installed

At the same time, you can develop .NET projects even for older versions like .NET Framework 2.0

This requirement only needs to be met for projects that reference the Pure.DI source code generator, other projects can use any version of C#.

Key features

Pure.DI is not a framework or library, but a source code generator for creating object graphs. To make them accurate, the developer uses a set of intuitive hints from the Pure.DI API. During the compilation phase, Pure.DI determines the optimal graph structure, checks its correctness, and generates partial class code to create object graphs in the Pure DI paradigm using only basic language constructs. The resulting generated code is robust, works everywhere, throws no exceptions, does not depend on .NET library calls or .NET reflections, is efficient in terms of performance and memory consumption, and is subject to all optimizations. This code can be easily integrated into an application because it does not use unnecessary delegates, additional calls to any methods, type conversions, boxing/unboxing, etc.

  • DI without any IoC/DI containers, frameworks, dependencies and hence no performance impact or side effects.

    Pure.DI is actually a .NET code generator. It uses basic language constructs to create simple code as well as if you were doing it yourself: de facto it's just a bunch of nested constructor calls. This code can be viewed, analyzed at any time, and debugged.

  • A predictable and verified dependency graph is built and validated on the fly while writing code.

    All logic for analyzing the graph of objects, constructors and methods takes place at compile time. Pure.DI notifies the developer at compile time of missing or cyclic dependencies, cases when some dependencies are not suitable for injection, etc. The developer has no chance to get a program that will crash at runtime because of some exception related to incorrect object graph construction. All this magic happens at the same time as the code is written, so you have instant feedback between the fact that you have made changes to your code and the fact that your code is already tested and ready to use.

  • Does not add any dependencies to other assemblies.

    When using pure DI, no dependencies are added to assemblies because only basic language constructs and nothing more are used.

  • Highest performance, including compiler and JIT optimization and minimal memory consumption.

    All generated code runs as fast as your own, in pure DI style, including compile-time and run-time optimization. As mentioned above, graph analysis is done at compile time, and at runtime there are only a bunch of nested constructors, and that's it. Memory is spent only on the object graph being created.

  • It works everywhere.

    Since the pure DI approach does not use any dependencies or .NET reflection at runtime, it does not prevent the code from running 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 this was a conscious decision: the main reason is that programmers don't need to learn a new API.

  • Superfine customization of generic types.

    In Pure.DI it is proposed to use special marker types instead of using open generic types. This allows you to build the object graph more accurately and take full advantage of generic types.

  • Supports the major .NET BCL types out of the box.

    Pure.DI already supports many of BCL types like Array, IEnumerable<T>, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ISet<T>, IProducerConsumerCollection<T>, ConcurrentBag<T>, Func<T>, ThreadLocal, ValueTask<T>, 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 building libraries or frameworks where resource consumption is particularly critical.

    Its high performance, zero memory consumption/preparation overhead, and lack of dependencies make it ideal for building libraries and frameworks.

Schrödinger's cat will demonstrate how it all works CSharp

The reality is

Cat

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

record CardboardBox<T>(T Content): IBox<T>;

class ShroedingersCat(Lazy<State> superposition): ICat
{
    // The decoherence of the superposition
    // at the time of observation via an irreversible process
    public State State => superposition.Value;
}

Note

Our abstraction and implementation knows nothing about the magic of DI or any frameworks.

Let's glue it all together

Add the Pure.DI package to your project:

NuGet

Let's bind the abstractions to their implementations and set up the creation of the object graph:

DI.Setup(nameof(Composition))
    // Models a random subatomic event that may or may not occur
    .Bind().As(Singleton).To<Random>()
    // Quantum superposition of two states: Alive or Dead
    .Bind().To((Random random) => (State)random.Next(2))
    .Bind().To<ShroedingersCat>()
    // Cardboard box with any contents
    .Bind().To<CardboardBox<TT>>()
    // Composition Root
    .Root<Program>("Root");

Note

In fact, the Bind().As(Singleton).To<Random>() binding is unnecessary since Pure.DI supports many .NET BCL types out of the box, including Random. It was added just for the example of using the Singleton lifetime.

The above code specifies the generation of a partial class named Composition, this name is defined in the DI.Setup(nameof(Composition)) call. This class contains a Root property that returns a graph of objects with an object of type Program as the root. The type and name of the property is defined by calling Root<Program>("Root"). The code of the generated class looks as follows:

partial class Composition
{
    private Lock _lock = new Lock();
    private Random? _random;
    
    public Program Root
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      get
      {
        var stateFunc = new Func<State>(() => {
              if (_random is null)
                using (_lock.EnterScope())
                  if (_random is null)
                    _random = new Random();

              return (State)_random.Next(2)
            });

        return new Program(
          new CardboardBox<ICat>(
            new ShroedingersCat(
              new Lazy<State>(
                stateFunc))));    
      }
    }

    public T Resolve<T>() { ... }

    public object Resolve(Type type) { ... }
}

The public Program Root { get; } property here is a Composition Root, the only place in the application where the composition of the object graph for the application takes place. Each instance is created by only basic language constructs, which compiles with all optimizations with minimal impact on performance and memory consumption. In general, applications may have multiple composition roots and thus such properties. Each composition root must have its own unique name, which is defined when the Root<T>(string name) method is called, as shown in the above code.

Time to open boxes!

class Program(IBox<ICat> box)
{
  // Composition Root, a single place in an application
  // where the composition of the object graphs
  // for an application take place
  static void Main() => new Composition().Root.Run();

  private void Run() => Console.WriteLine(box);
}

Note

Pure.DI creates efficient code in a pure DI paradigm, using only basic language constructs as if you were writing code by hand. This allows you to take full advantage of Dependency Injection everywhere and always, without any compromise!

The full analog of this application with top-level statements can be found here.

Just try!

Install the projects template

dotnet new install Pure.DI.Templates

In some directory, create a console application

dotnet new di

And run it

dotnet run

Examples

Basics

Lifetimes

Base Class Library

Generics

Attributes

Interception

Hints

Advanced

Applications

Generated Code

Each generated class, hereafter called a composition, must be customized. Setup starts with a call to the Setup(string compositionTypeName) method:

DI.Setup("Composition")
    .Bind<IDependency>().To<Dependency>()
    .Bind<IService>().To<Service>()
    .Root<IService>("Root");
The following class will be generated
partial class Composition
{
    // Default constructor
    public Composition() { }

    // Scope constructor
    internal Composition(Composition parentScope) { }

    // Composition root
    public IService Root
    {
        get
        {
            return new Service(new Dependency());
        }
    }

    public T Resolve<T>()  { ... }

    public T Resolve<T>(object? tag)  { ... }

    public object Resolve(Type type) { ... }

    public object Resolve(Type type, object? tag) { ... }
}

The compositionTypeName parameter can be omitted

  • if the setup is performed inside a partial class, then the composition will be created for this partial class
  • for the case of a class with composition kind CompositionKind.Global, see this example
Setup arguments

The first parameter is used to specify the name of the composition class. All sets with the same name will be combined to create one composition class. Alternatively, this name may contain a namespace, e.g. a composition class is generated for Sample.Composition:

namespace Sample
{
    partial class Composition
    {
        ...
    }
}

The second optional parameter may have multiple values to determine the kind of composition.

CompositionKind.Public

This value is used by default. If this value is specified, a normal composition class will be created.

CompositionKind.Internal

If you specify this value, the class will not be generated, but this setup can be used by others as a base setup. For example:

DI.Setup("BaseComposition", CompositionKind.Internal)
    .Bind().To<Dependency>();

DI.Setup("Composition").DependsOn("BaseComposition")
    .Bind().To<Service>();    

If the CompositionKind.Public flag is set in the composition setup, it can also be the base for other compositions, as in the example above.

CompositionKind.Global

No composition class will be created when this value is specified, but this setup is the base setup for all setups in the current project, and DependsOn(...) is not required.

Constructors

Default constructor

It's quite trivial, this constructor simply initializes the internal state.

Parameterized constructor

It replaces the default constructor and is only created if at least one argument is specified. For example:

DI.Setup("Composition")
    .Arg<string>("name")
    .Arg<int>("id")
    ...

In this case, the constructor with arguments is as follows:

public Composition(string name, int id) { ... }

and there is no default constructor. It is important to remember that only those arguments that are used in the object graph will appear in the constructor. Arguments that are not involved cannot be defined, as they are omitted from the constructor parameters to save resources.

Scope constructor

This constructor creates a composition instance for the new scope. This allows Lifetime.Scoped to be applied. See this example for details.

Composition Roots

Public Composition Roots

To create an object graph quickly and conveniently, a set of properties (or a methods) is formed. These properties/methods are here called roots of compositions. The type of a property/method is the type of the root object created by the composition. Accordingly, each invocation of a property/method leads to the creation of a composition with a root element of this type.

DI.Setup("Composition")
    .Bind<IService>().To<Service>()
    .Root<IService>("MyService");

In this case, the property for the IService type 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 the root of IService type:

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

If the root name is empty, a private composition root with a random name is created:

private IService RootM07D16di_0001
{
    get { ... }
}

This root is available in Resolve methods in the same way as public roots. For example:

DI.Setup("Composition")
    .Bind<IService>().To<Service>()
    .Root<IService>();

These properties have an arbitrary name and access modifier private and cannot be used directly from the code. Do not attempt to use them, as their names are arbitrarily changed. Private composition roots can be resolved by Resolve methods.

Methods "Resolve"

Methods "Resolve"

By default, a set of four Resolve methods is 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 both public and private composition roots that do not depend on any arguments of the composition roots. They are useful when using the Service Locator approach, where the code resolves composition roots in place:

var composition = new Composition();

composition.Resolve<IService>();

This is a not recommended way to create composition roots because Resolve methods have a number of disadvantages:

  • They provide access to an unlimited set of dependencies.
  • Their use can potentially lead to runtime exceptions, for example, when the corresponding root has not been defined.
  • Lead to performance degradation because they search for the root of a composition based on its type.

To control the generation of these methods, see the Resolve hint.

Dispose and DisposeAsync

Provides a mechanism to release unmanaged resources. These methods are generated only if the composition contains at least one singleton/scoped instance that implements either the IDisposable and/or DisposeAsync interface. The Dispose() or DisposeAsync() method of the composition should be called to dispose of all created singleton/scoped objects:

using var composition = new Composition();

or

await using var composition = new Composition();

To dispose objects of other lifetimes please see this or this examples.

Setup hints

Setup hints

Hints are used to fine-tune code generation. Setup hints can be used as shown 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 commented out before the Setup method as hint = value. For example:

// Resolve = Off
// ThreadSafe = Off
DI.Setup("Composition")
    ...

Both approaches can be mixed:

// Resolve = Off
DI.Setup("Composition")
    .Hint(Hint.ThreadSafe, "Off")
    ...
Hint Values C# version Default
Resolve On or Off On
OnNewInstance On or Off 9.0 Off
OnNewInstancePartial On or Off On
OnNewInstanceImplementationTypeNameRegularExpression Regular expression .+
OnNewInstanceTagRegularExpression Regular expression .+
OnNewInstanceLifetimeRegularExpression Regular expression .+
OnDependencyInjection On or Off 9.0 Off
OnDependencyInjectionPartial On or Off On
OnDependencyInjectionImplementationTypeNameRegularExpression Regular expression .+
OnDependencyInjectionContractTypeNameRegularExpression Regular expression .+
OnDependencyInjectionTagRegularExpression Regular expression .+
OnDependencyInjectionLifetimeRegularExpression Regular expression .+
OnCannotResolve On or Off 9.0 Off
OnCannotResolvePartial On or Off On
OnCannotResolveContractTypeNameRegularExpression Regular expression .+
OnCannotResolveTagRegularExpression Regular expression .+
OnCannotResolveLifetimeRegularExpression Regular expression .+
OnNewRoot On or Off Off
OnNewRootPartial On or Off On
ToString On or Off Off
ThreadSafe On or Off On
ResolveMethodModifiers Method modifier public
ResolveMethodName Method name Resolve
ResolveByTagMethodModifiers Method modifier public
ResolveByTagMethodName Method name Resolve
ObjectResolveMethodModifiers Method modifier public
ObjectResolveMethodName Method name Resolve
ObjectResolveByTagMethodModifiers Method modifier public
ObjectResolveByTagMethodName Method name Resolve
DisposeMethodModifiers Method modifier public
DisposeAsyncMethodModifiers Method modifier public
FormatCode On or Off Off
SeverityOfNotImplementedContract Error or Warning or Info or Hidden Error
Comments On or Off On

The list of hints will be gradually expanded to meet the needs and desires for fine-tuning code generation. Please feel free to add your ideas.

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 the generation time of the class composition, and in this case no private composition roots will be generated. The class composition will be smaller and will only have public roots. When the Resolve hint is disabled, only the public roots properties are available, so be sure to explicitly define them using the Root<T>(string name) method with an explicit composition root name.

OnNewInstance Hint

Determines whether to use the OnNewInstance partial method. By default, this partial method is not generated. This can be useful, for example, for logging purposes:

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 with a T type, where T is the actual type of the created instance. To minimize performance loss when calling OnNewInstance, use the three hints below.

OnNewInstancePartial Hint

Determines whether to generate the OnNewInstance partial method. By default, this partial method is generated when the OnNewInstance hint is On.

OnNewInstanceImplementationTypeNameRegularExpression Hint

This is a regular expression for filtering by instance type name. This hint is useful when OnNewInstance is in On state and it is necessary to limit the set of types for which the OnNewInstance method will be called.

OnNewInstanceTagRegularExpression Hint

This is a regular expression for filtering by tag. This hint is also useful when OnNewInstance is in On state and it is necessary to limit the set of tags for which the OnNewInstance method will be called.

OnNewInstanceLifetimeRegularExpression Hint

This is a regular expression for filtering by lifetime. This hint is also useful when OnNewInstance is in On state and it is necessary to restrict the set of life times for which the OnNewInstance method will be called.

OnDependencyInjection Hint

Determines whether to use the OnDependencyInjection partial method when the OnDependencyInjection hint is On to control dependency injection. By default it is On.

// OnDependencyInjection = On
// OnDependencyInjectionPartial = Off
// OnDependencyInjectionContractTypeNameRegularExpression = ICalculator[\d]{1}
// OnDependencyInjectionTagRegularExpression = Abc
DI.Setup("Composition")
    ...

OnDependencyInjectionPartial Hint

Determines whether to generate the OnDependencyInjection partial method to control dependency injection. By default, this partial method is not generated. It cannot have an empty body because of the return value. It must be overridden when it is generated. This may be useful, for example, for Interception Scenario.

// OnDependencyInjection = On
// OnDependencyInjectionContractTypeNameRegularExpression = ICalculator[\d]{1}
// OnDependencyInjectionTagRegularExpression = Abc
DI.Setup("Composition")
    ...

To minimize performance loss when calling OnDependencyInjection, use the three tips below.

OnDependencyInjectionImplementationTypeNameRegularExpression Hint

This is a regular expression for filtering by instance type name. This hint is useful when OnDependencyInjection is in On state and it is necessary to restrict the set of types for which the OnDependencyInjection method will be called.

OnDependencyInjectionContractTypeNameRegularExpression Hint

This is a regular expression for filtering by the name of the resolving type. This hint is also useful when OnDependencyInjection is in On state and it is necessary to limit the set of permissive types for which the OnDependencyInjection method will be called.

OnDependencyInjectionTagRegularExpression Hint

This is a regular expression for filtering by tag. This hint is also useful when OnDependencyInjection is in the On state and you want to limit the set of tags for which the OnDependencyInjection method will be called.

OnDependencyInjectionLifetimeRegularExpression Hint

This is a regular expression for filtering by lifetime. This hint is also useful when OnDependencyInjection is in On state and it is necessary to restrict the set of lifetime for which the OnDependencyInjection method will be called.

OnCannotResolve Hint

Determines whether to use the OnCannotResolve<T>(...) partial method to handle a scenario in which an instance cannot be resolved. By default, this partial method is not generated. Because of the return value, it cannot have an empty body and must be overridden at creation.

// OnCannotResolve = On
// OnCannotResolveContractTypeNameRegularExpression = string|DateTime
// OnDependencyInjectionTagRegularExpression = null
DI.Setup("Composition")
    ...

To avoid missing failed bindings by mistake, use the two relevant hints below.

OnCannotResolvePartial Hint

Determines whether to generate the OnCannotResolve<T>(...) partial method when the OnCannotResolve hint is On to handle a scenario in which an instance cannot be resolved. By default it is On.

// OnCannotResolve = On
// OnCannotResolvePartial = Off
// OnCannotResolveContractTypeNameRegularExpression = string|DateTime
// OnDependencyInjectionTagRegularExpression = null
DI.Setup("Composition")
    ...

To avoid missing failed bindings by mistake, use the two relevant hints below.

OnNewRoot Hint

Determines whether to use a static partial method OnNewRoot<TContract, T>(...) to handle the new composition root registration event.

// OnNewRoot = On
DI.Setup("Composition")
    ...

Be careful, this hint disables checks for the ability to resolve dependencies!

OnNewRootPartial Hint

Determines whether to generate a static partial method OnNewRoot<TContract, T>(...) when the OnNewRoot hint is On to handle the new composition root registration event.

// OnNewRootPartial = Off
DI.Setup("Composition")
    ...

OnCannotResolveContractTypeNameRegularExpression Hint

This is a regular expression for filtering by the name of the resolving type. This hint is also useful when OnCannotResolve is in On state and it is necessary to limit the set of resolving types for which the OnCannotResolve method will be called.

OnCannotResolveTagRegularExpression Hint

This is a regular expression for filtering by tag. This hint is also useful when OnCannotResolve is in On state and it is necessary to limit the set of tags for which the OnCannotResolve method will be called.

OnCannotResolveLifetimeRegularExpression Hint

This is a regular expression for filtering by lifetime. This hint is also useful when OnCannotResolve is in the On state and it is necessary to restrict the set of lives for which the OnCannotResolve method will be called.

ToString Hint

Determines whether to generate the ToString() method. This method provides a class diagram in mermaid format. 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 way. The default value of this hint is On. It is a good practice not to use threads when creating an object graph, in this case the hint can be disabled, which will result in a small performance gain. For example:

// ThreadSafe = Off
DI.Setup("Composition")
    .Bind<IService>().To<Service>()
    .Root<IService>("MyService");

ResolveMethodModifiers Hint

Overrides the modifiers of the public T Resolve<T>() method.

ResolveMethodName Hint

Overrides the method name for public T Resolve<T>().

ResolveByTagMethodModifiers Hint

Overrides the modifiers of the public T Resolve<T>(object? tag) method.

ResolveByTagMethodName Hint

Overrides the method name for public T Resolve<T>(object? tag).

ObjectResolveMethodModifiers Hint

Overrides the modifiers of the public object Resolve(Type type) method.

ObjectResolveMethodName Hint

Overrides the method name for public object Resolve(Type type).

ObjectResolveByTagMethodModifiers Hint

Overrides the modifiers of the public object Resolve(Type type, object? tag) method.

ObjectResolveByTagMethodName Hint

Overrides the method name for public object Resolve(Type type, object? tag).

DisposeMethodModifiers Hint

Overrides the modifiers of the public void Dispose() method.

DisposeAsyncMethodModifiers Hint

Overrides the modifiers of the public ValueTask DisposeAsync() method.

FormatCode Hint

Specifies whether the generated code should be formatted. This option consumes a lot of CPU resources. This hint may be useful when studying the generated code or, for example, when making presentations.

SeverityOfNotImplementedContract Hint

Indicates the severity level of the situation when, in the binding, an implementation does not implement a contract. Possible values:

  • "Error", it is default value.
  • "Warning" - something suspicious but allowed.
  • "Info" - information that does not indicate a problem.
  • "Hidden" - what's not a problem.

Comments Hint

Specifies whether the generated code should be commented.

// Represents the composition class
DI.Setup(nameof(Composition))
    .Bind<IService>().To<Service>()
    // Provides a composition root of my service
    .Root<IService>("MyService");

Appropriate comments will be added to the generated Composition class and the documentation for the class, depending on the IDE used, will look something like this:

ReadmeDocumentation1.png

Then documentation for the composition root:

ReadmeDocumentation2.png

Code generation workflow
flowchart TD
    start@{ shape: circle, label: "Start" }
    setups[fa:fa-search DI setups analysis]
    types["`fa:fa-search Types analysis
    constructors/methods/properties/fields`"] 
    subgraph dep[Dependency graph]
    option[fa:fa-search Selecting a next dependency set]
    creating[fa:fa-cog Creating a dependency graph variant]
    verification{fa:fa-check-circle Verification}
    end
    codeGeneration[fa:fa-code Code generation]
    compilation[fa:fa-cog Compilation]
    failed@{ shape: dbl-circ, label: "fa:fa-thumbs-down Compilation failed" }
    success@{ shape: dbl-circ, label: "fa:fa-thumbs-up Success" }

    start ==> setups
    setups -.->|Has problems| failed
    setups ==> types
    types -.-> |Has problems| failed
    types ==> option
    option ==> creating
    option -.-> |There are no other options| failed
    creating ==> verification
    verification -->|Has problems| option
    verification ==>|Correct| codeGeneration
    codeGeneration ==> compilation
    compilation -.-> |Has problems| failed
    compilation ==> success
Loading

NuGet packages

Pure.DI NuGet DI Source code generator
Pure.DI.Abstractions NuGet Abstractions for Pure.DI
Pure.DI.Templates NuGet Template Package you can call from the shell/command line.
Pure.DI.MS NuGet Tools for working with Microsoft DI

Project template

Install the DI template Pure.DI.Templates

dotnet new install Pure.DI.Templates

Create a "Sample" console application from the template di

dotnet new di -o ./Sample

And run it

dotnet run --project Sample

For more information about the template, please see this page.

Troubleshooting

Version update

When updating the version, it is possible that the previous version of the code generator remains active and is used by compilation services. In this case, the old and new versions of the generator may conflict. For a project where the code generator is used, it is recommended to do the following:

  • After updating the version, close the IDE if it is open
  • Delete the obj and bin directories
  • Execute the following commands one by one
dotnet build-server shutdown
dotnet restore
dotnet build
Disabling API generation

Pure.DI automatically generates its API. If an assembly already has the Pure.DI API, for example, from another assembly, it is sometimes necessary to disable its automatic generation to avoid ambiguity. To do this, you need to add a DefineConstants element to the project files of these modules. For example:

<PropertyGroup>
    <DefineConstants>$(DefineConstants);PUREDI_API_SUPPRESSION</DefineConstants>
</PropertyGroup>
Display generated files

You can set project properties to save generated files and control their storage location. In the project file, add the <EmitCompilerGeneratedFiles> element to the <PropertyGroup> group and set its value to true. Build the project again. The generated files are now created in the obj/Debug/netX.X/generated/Pure.DI/Pure.DI/Pure.DI.SourceGenerator directory. The path components correspond to the build configuration, the target framework, the source generator project name, and the full name of the generator type. You can choose a more convenient output folder by adding the <CompilerGeneratedFilesOutputPath> element to the application project file. For example:

<Project Sdk="Microsoft.NET.Sdk">
    
    <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
    
</Project>

Contribution

Thank you for your interest in contributing to the Pure.DI project! First of all, if you are going to make a big change or feature, please open a problem first. That way, we can coordinate and understand if the change you're going to work on fits with current priorities and if we can commit to reviewing and merging it within a reasonable timeframe. We don't want you to waste a lot of your valuable time on something that may not align with what we want for Pure.DI.

The entire build logic is a regular console .NET application. You can use the build.cmd and build.sh files with the appropriate command in the parameters to perform all basic actions on the project, e.g:

Command Description
g, generator Builds and tests generator
l, libs Builds and tests libraries
c, check Compatibility checks
p, pack Creates NuGet packages
r, readme Generates README.md
benchmarks, bm Runs benchmarks
deploy, dp Deploys packages
t, template Creates and deploys templates
u, upgrade Upgrading the internal version of DI to the latest public version

For example:

./build.sh pack
./build.cmd benchmarks

If you are using the Rider IDE, it already has a set of configurations to run these commands.

Note

This project uses C# interactive build automation system for .NET

This tool helps to make .NET builds more efficient.

Contribution Prerequisites:

Installed .NET SDK 9.0

Additional resources

RU DotNext video

DotNext Pure.DI

C# interactive build automation system for .NET

Examples of how to set up a composition

Articles

Benchmarks

Array
Method Mean ErrorStdDevMedianRatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Pure.DI composition root'88.33 ns1.075 ns1.006 ns88.58 ns0.940.030.0377-632 B1.00
'Pure.DI Resolve<T>()'91.94 ns1.832 ns2.445 ns91.66 ns0.980.040.0377-632 B1.00
'Pure.DI Resolve(Type)'92.65 ns2.244 ns6.366 ns89.18 ns0.980.070.0377-632 B1.00
'Hand Coded'94.24 ns1.883 ns2.578 ns94.06 ns1.000.040.0377-632 B1.00
DryIoc99.10 ns0.999 ns0.834 ns98.91 ns1.050.030.0377-632 B1.00
LightInject103.24 ns2.119 ns4.183 ns102.65 ns1.100.050.0377-632 B1.00
Unity4,510.09 ns74.628 ns66.155 ns4,488.31 ns47.891.470.86210.007614520 B22.97
Autofac15,134.66 ns110.608 ns86.355 ns15,131.24 ns160.724.451.70900.061028976 B45.85

Array details

Enum
Method Mean ErrorStdDevMedianRatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Pure.DI composition root'64.36 ns1.282 ns2.919 ns62.75 ns1.000.050.0205-344 B1.00
'Hand Coded'64.49 ns0.791 ns0.661 ns64.47 ns1.000.010.0205-344 B1.00
'Pure.DI Resolve<T>()'65.26 ns0.965 ns0.903 ns65.56 ns1.010.020.0205-344 B1.00
'Pure.DI Resolve(Type)'65.43 ns1.004 ns0.784 ns65.23 ns1.010.020.0205-344 B1.00
'Microsoft DI'93.40 ns1.322 ns1.172 ns92.96 ns1.450.020.0281-472 B1.37
LightInject147.58 ns1.650 ns1.544 ns147.96 ns2.290.030.0510-856 B2.49
DryIoc147.61 ns1.110 ns0.984 ns147.54 ns2.290.030.0510-856 B2.49
Unity3,736.48 ns73.272 ns68.539 ns3,739.88 ns57.941.180.82020.007613752 B39.98
Autofac15,610.79 ns288.580 ns595.967 ns15,436.50 ns242.089.471.73950.061029104 B84.60

Enum details

Func
Method Mean ErrorStdDevRatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Pure.DI composition root'4.382 ns0.0561 ns0.0525 ns0.840.010.0014-24 B1.00
'Hand Coded'5.193 ns0.0649 ns0.0542 ns1.000.010.0014-24 B1.00
'Pure.DI Resolve<T>()'5.914 ns0.0753 ns0.0667 ns1.140.020.0014-24 B1.00
'Pure.DI Resolve(Type)'6.375 ns0.0465 ns0.0388 ns1.230.010.0014-24 B1.00
DryIoc28.979 ns0.3875 ns0.3435 ns5.580.080.0072-120 B5.00
LightInject155.185 ns2.9628 ns2.7714 ns29.890.600.0300-504 B21.00
Unity1,760.400 ns12.4774 ns11.6714 ns339.033.990.1507-2552 B106.33
Autofac5,745.474 ns36.1642 ns30.1988 ns1,106.5112.280.83160.007614008 B583.67

Func details

Singleton
Method Mean Error StdDevRatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Hand Coded'3.016 ns0.0721 ns0.0602 ns1.000.030.0014-24 B1.00
'Pure.DI composition root'3.337 ns0.0778 ns0.0728 ns1.110.030.0014-24 B1.00
'Pure.DI Resolve<T>()'3.517 ns0.0602 ns0.0533 ns1.170.030.0014-24 B1.00
'Pure.DI Resolve(Type)'4.649 ns0.1417 ns0.3832 ns1.540.130.0014-24 B1.00
DryIoc11.388 ns0.0389 ns0.0304 ns3.780.070.0014-24 B1.00
'Simple Injector'16.630 ns0.0782 ns0.0693 ns5.520.110.0014-24 B1.00
'Microsoft DI'19.551 ns0.0684 ns0.0606 ns6.490.120.0014-24 B1.00
LightInject426.861 ns1.0945 ns0.9702 ns141.592.700.0014-24 B1.00
Unity2,645.530 ns47.7345 ns66.9171 ns877.5127.400.1869-3184 B132.67
Autofac9,723.452 ns60.9124 ns53.9972 ns3,225.2463.411.43430.045824208 B1,008.67
'Castle Windsor'16,991.331 ns93.5701 ns87.5255 ns5,635.97110.231.4038-23912 B996.33
Ninject67,297.995 ns1,045.7465 ns927.0271 ns22,322.54516.334.27251.098673176 B3,049.00

Singleton details

Transient
Method Mean Error StdDev RatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Pure.DI composition root'3.491 ns0.1203 ns0.2764 ns0.970.090.0014-24 B1.00
'Hand Coded'3.619 ns0.1132 ns0.1952 ns1.000.070.0014-24 B1.00
'Pure.DI Resolve<T>()'4.417 ns0.1377 ns0.3429 ns1.220.110.0014-24 B1.00
'Pure.DI Resolve(Type)'4.649 ns0.1431 ns0.1338 ns1.290.080.0014-24 B1.00
LightInject7.043 ns0.0902 ns0.0754 ns1.950.100.0014-24 B1.00
'Microsoft DI'10.357 ns0.1357 ns0.1133 ns2.870.150.0014-24 B1.00
DryIoc10.673 ns0.1197 ns0.1119 ns2.960.160.0014-24 B1.00
'Simple Injector'14.594 ns0.1373 ns0.1217 ns4.040.210.0014-24 B1.00
Unity4,320.577 ns85.0619 ns175.6675 ns1,197.1378.580.3052-5176 B215.67
Autofac12,616.111 ns227.4971 ns201.6703 ns3,495.62189.021.98360.091633224 B1,384.33
'Castle Windsor'27,766.858 ns312.7020 ns277.2021 ns7,693.52405.543.23490.030554360 B2,265.00
Ninject148,286.554 ns3,309.2559 ns9,600.7521 ns41,086.583,398.597.56841.4648128736 B5,364.00

Transient details

Benchmarks environment

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.4894/22H2/2022Update)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100
  [Host]     : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  DefaultJob : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2