GithubHelp home page GithubHelp logo

devteam / pure.di Goto Github PK

View Code? Open in Web Editor NEW
384.0 5.0 21.0 62.83 MB

Pure DI for .NET without frameworks!

License: MIT License

C# 98.71% Batchfile 0.03% Shell 0.01% Kotlin 0.02% HTML 1.24%
dependency-injection ioc solid inversion-of-control di csharp-sourcegenerator dotnet injection-container injection-framework pure

pure.di's Introduction

Pure DI for .NET

NuGet License Build Build

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 about missing or ring 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

class CardboardBox<T>(T content) : IBox<T>
{
    public T Content { get; } = content;

    public override string ToString() => $"[{Content}]";
}

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;

    public override string ToString() => $"{State} cat";
}

It is important to note that 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>()
    // Represents a quantum superposition of 2 states: Alive or Dead
    .Bind().To(ctx =>
    {
      ctx.Inject<Random>(out var random);
      return (State)random.Next(2);
    })
    .Bind().To<ShroedingersCat>()
    // Represents a cardboard box with any contents
    .Bind().To<CardboardBox<TT>>()
    // Composition Root
    .Root<Program>("Root");

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 object _lock = new object();
    private Random _random;    
    
    public Program Root
    {
      get
      {
        Func<State> stateFunc = new Func<State>(() =>
        {
          if (_random == null)
          {
            lock (_lock)
            {
              if (_random == null)
              {
                _random = new Random();
              }
            }
          }
          
          return (State)_random.Next(2);      
        });
        
        return new Program(
          new CardboardBox<ICat>(
            new ShroedingersCat(
              new Lazy<Sample.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);
}

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

To summarize

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!

Just try!

Download a sample project

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

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 baseComposition) { }

    // 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) { ... }
}
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<IDependency>().To<Dependency>();

DI.Setup("Composition").DependsOn("BaseComposition")
    .Bind<IService>().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.

Properties

Public Composition Roots

To create an object graph quickly and conveniently, a set of properties (or a methods) is formed. These properties 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

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. To control the generation of these methods, see the Resolve hint.

Dispose

Provides a mechanism to release unmanaged resources. This method is generated only if the composition contains at least one singleton instance that implements the IDisposable interface. To dispose of all created singleton objects, the Dispose() method of the composition should be called:

using var composition = new Composition();
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")
    .Hint(Hint.ToString, "On")
    ...

Both approaches can be used in combination with each other.

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
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.

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

NuGet packages

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

Requirements for development environments

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

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.

This project uses the "build as code" approach using csharp-interactive. 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, update Updates internal DI 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.

Contribution Prerequisites

Installed .NET SDK 8.0

Benchmarks

Array
Method Mean ErrorStdDevRatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Hand Coded'168.1 ns1.72 ns1.44 ns1.000.000.0336-632 B1.00
'Pure.DI composition root'169.6 ns3.08 ns2.58 ns1.010.020.0336-632 B1.00
'Pure.DI Resolve<T>()'170.7 ns3.02 ns2.52 ns1.020.020.0336-632 B1.00
'Pure.DI Resolve(Type)'171.2 ns2.44 ns2.04 ns1.020.020.0336-632 B1.00
LightInject179.8 ns3.43 ns3.21 ns1.070.020.0336-632 B1.00
DryIoc200.5 ns3.89 ns4.16 ns1.200.020.0336-632 B1.00
Unity9,966.0 ns80.16 ns62.58 ns59.260.420.7629-14520 B22.97
Autofac27,170.5 ns308.31 ns257.45 ns161.601.871.52590.061028816 B45.59

Array details

Enum
Method Mean ErrorStdDevRatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Pure.DI composition root'128.2 ns1.50 ns1.25 ns0.910.010.0184-344 B1.00
'Pure.DI Resolve(Type)'130.5 ns2.22 ns1.85 ns0.930.020.0184-344 B1.00
'Pure.DI Resolve<T>()'133.8 ns2.38 ns1.99 ns0.950.010.0184-344 B1.00
'Hand Coded'140.3 ns1.69 ns1.41 ns1.000.000.0184-344 B1.00
'Microsoft DI'181.3 ns2.01 ns1.67 ns1.290.020.0250-472 B1.37
LightInject277.7 ns3.47 ns2.89 ns1.980.030.0458-856 B2.49
DryIoc281.7 ns5.33 ns4.45 ns2.010.030.0458-856 B2.49
Unity7,768.1 ns124.10 ns152.41 ns55.431.240.7324-13752 B39.98
Autofac26,954.8 ns529.73 ns543.99 ns192.634.871.52590.061028944 B84.14

Enum details

Func
Method Mean ErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
'Pure.DI composition root'8.002 ns0.1496 ns0.1326 ns0.850.010.001324 B1.00
'Hand Coded'9.445 ns0.1727 ns0.1615 ns1.000.000.001324 B1.00
'Pure.DI Resolve<T>()'10.327 ns0.2801 ns0.2620 ns1.090.020.001324 B1.00
'Pure.DI Resolve(Type)'11.954 ns0.2458 ns0.2053 ns1.260.030.001324 B1.00
DryIoc62.785 ns1.1086 ns0.9828 ns6.650.190.0063120 B5.00
LightInject296.638 ns3.9363 ns3.2870 ns31.370.800.0267504 B21.00
Unity4,448.473 ns35.3598 ns33.0756 ns471.119.330.12972552 B106.33
Autofac10,857.806 ns115.9508 ns96.8241 ns1,147.9522.400.747714008 B583.67

Func details

Singleton
Method Mean Error StdDev RatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Hand Coded'7.600 ns0.1685 ns0.1316 ns1.000.000.0013-24 B1.00
'Pure.DI composition root'8.652 ns0.2436 ns0.2392 ns1.140.030.0013-24 B1.00
'Pure.DI Resolve<T>()'9.919 ns0.2579 ns0.2154 ns1.300.040.0013-24 B1.00
'Pure.DI Resolve(Type)'11.393 ns0.2199 ns0.1836 ns1.500.040.0013-24 B1.00
DryIoc27.581 ns0.3758 ns0.3138 ns3.630.070.0013-24 B1.00
'Simple Injector'33.807 ns0.3441 ns0.3050 ns4.450.100.0013-24 B1.00
'Microsoft DI'38.478 ns0.3053 ns0.2549 ns5.060.070.0013-24 B1.00
LightInject865.775 ns1.2567 ns1.0494 ns113.961.960.0010-24 B1.00
Unity7,677.459 ns99.8336 ns88.4999 ns1,011.6717.400.1678-3184 B132.67
Autofac18,825.341 ns315.1815 ns263.1908 ns2,478.0062.671.28170.030524208 B1,008.67
'Castle Windsor'31,318.225 ns408.7010 ns362.3028 ns4,123.5674.241.2207-23912 B996.33
Ninject116,099.082 ns2,240.6371 ns2,991.1861 ns15,609.54360.853.90630.976674096 B3,087.33

Singleton details

Transient
Method Mean Error StdDev RatioRatioSDGen0Gen1AllocatedAlloc Ratio
'Pure.DI composition root'7.903 ns0.1254 ns0.0979 ns0.970.020.0013-24 B1.00
'Hand Coded'8.135 ns0.1484 ns0.1158 ns1.000.000.0013-24 B1.00
'Pure.DI Resolve<T>()'10.337 ns0.2882 ns0.3848 ns1.280.040.0013-24 B1.00
'Pure.DI Resolve(Type)'11.442 ns0.2251 ns0.1995 ns1.410.030.0013-24 B1.00
LightInject20.096 ns0.0607 ns0.0507 ns2.470.040.0013-24 B1.00
'Microsoft DI'25.671 ns0.1087 ns0.0849 ns3.160.050.0013-24 B1.00
DryIoc27.474 ns0.5736 ns0.4478 ns3.380.070.0013-24 B1.00
'Simple Injector'34.814 ns0.2384 ns0.2114 ns4.280.050.0013-24 B1.00
Unity11,090.606 ns65.9897 ns61.7268 ns1,366.0819.490.2747-5176 B215.67
Autofac28,646.903 ns355.7947 ns315.4027 ns3,520.2461.911.77000.091633224 B1,384.33
'Castle Windsor'58,534.573 ns988.3103 ns876.1114 ns7,196.49162.412.8687-54360 B2,265.00
Ninject253,820.129 ns4,777.4163 ns5,310.0855 ns31,080.38673.816.83591.4648131008 B5,458.67

Transient details

Benchmarks environment

BenchmarkDotNet v0.13.12, Ubuntu 20.04.6 LTS (Focal Fossa)
Intel Xeon Platinum 8259CL CPU 2.50GHz, 1 CPU, 2 logical cores and 1 physical core
.NET SDK 8.0.201
  [Host]     : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX-512F+CD+BW+DQ+VL
  DefaultJob : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX-512F+CD+BW+DQ+VL

pure.di's People

Contributors

misinformeddna avatar nikolaypianikov avatar serg046 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pure.di's Issues

Unclear error DI007 when compilation errors were occurred

Shroedinger cat sample. Create separate classlib and move 3 classes to there State, ICat and ShroedingersCat
following error

error DI007: Sample.ShroedingersCat is not inherited from Sample.ICat

If I move ShroedingersCat back to console app, everything working again.

Ability to register composition roots with fewer API calls

Right now:

DI.Setup(nameof(Composition))
  .Bind<IService>().To<Service>()
  .Bind<IService>("Other").To<OtherService>()
  .Root<IService>("MyRoot")
  .Root<IService>("SomeOtherService", "Other");

An API such as this is proposed:

DI.Setup(nameof(Composition))
  .RootBind<IService>("MyRoot").To<Service>()
  .RootBind<IService>("SomeOtherService", "Other").To<OtherService>()

Thus we could add a new API method to IConfiguration like:

IBinding RootBind<T>(string name = "", object tag = null, RootKinds kind = RootKinds.Default);

For integration tests and local debugging, you can use this project.

Major changes are likely to be here.

We also need an example in the documentation. It can be done here.

Running benchmarks on ServiceCollection results in an InvalidCastException

$ .\Pure.DI.Benchmark.exe ServiceCollection
Unhandled exception. System.InvalidCastException: Unable to cast object of type 'Pure.DI.Benchmark.Benchmarks.ServiceCollection' to type 'Pure.DI.Benchmark.BenchmarkBase'.
at Pure.DI.Benchmark.Program.Main(String[] args) in C:\repos\Pure.DI\Pure.DI.Benchmark\Program.cs:line 26

I'm going to attempt a PR.

C#12 collection expressions are not supported

C# 12 brings a new way to instantiate collections via brackets like this [1,2,3]. Currently this is not supported so that Bind<IDependency>(tags: "tag").To<Dependency>() works fine but Bind<IDependency>(tags: ["tag"]).To<Dependency>() fails with "DIE001 ["tag"] must be a constant value of type System.Object.". Btw, C# treats it as a constant, you can use such a value in attributes, etc.

Compilation error Microsoft.CodeAnalysis.CSharp.CSharpCompilation.Update

I did try

  1. create new console app
  2. copy code from ShroedingersCat sample.
  3. Add reference to Pure.DI
    and receive following error.
1>C:\Program Files\dotnet\sdk\7.0.100-preview.5.22267.11\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIdentifierInference.targets(216,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
1>Skipping analyzers to speed up the build. You can execute 'Build' or 'Rebuild' command to run analyzers.
1>CSC : error DI002: System.ArgumentException: Inconsistent syntax tree features Parameter name: trees
    at Microsoft.CodeAnalysis.Compilation.SyntaxTreeCommonFeatures(IEnumerable`1 trees)
    at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.Update(ReferenceManager referenceManager, Boolean reuseReferenceManager, SyntaxAndDeclarationManager syntaxAndDeclarations)
    at Pure.DI.Core.MetadataBuilder.Build(IExecutionContext executionContext)
    at Pure.DI.Core.SourceBuilder.Build(IExecutionContext executionContext)
    at Pure.DI.Core.Generator.Generate(IExecutionContext context)
1>D:\d\scratch\ConsoleApp1\ConsoleApp1\Program.cs(13,11,13,15): error CS0246: The type or namespace name 'Pure' could not be found (are you missing a using directive or an assembly reference?)
1>D:\d\scratch\ConsoleApp1\ConsoleApp1\Program.cs(14,18,14,22): error CS0246: The type or namespace name 'Pure' could not be found (are you missing a using directive or an assembly reference?)

after I rebuild project, everything start working.

Invalid code with duplicate field names is generated in case if multiple roots are dependent on external argument

Description

I am using Pure.DI 2.1.1 and I have a registration scheme with multiple roots exposed.
I pass IExternalDependency as an argument to the Composition constructor, and multiple types have a dependency on this IExternalDependency.
However, this configuration leads to invalid code being generated, and I see the following errors during the compilation phase:

Error CS0100: The parameter name 'externalDependency' is a duplicate
Error CS0102: The type 'Composition' already contains a definition for '_argM02D18di_externalDependency'

Example

DI.Setup("Composition")
    .Bind<IService1>().As(Lifetime.Singleton).To<Service1>()
    .Bind<IService2>().As(Lifetime.Singleton).To<Service2>()
    .Bind<IRootService>().As(Lifetime.Singleton).To<RootService>()
    .Arg<IExternalDependency>("externalDependency")
    .Root<IService1>("Service1")
    .Root<IRootService>("Root");

Repository

tests/Pure.DI.UsageTests/Basics/ArgumentsScenario2.cs

Using external values when resolving

Hi

While trying this out I ran into an issue that I've been unable to solve.

Most of our services needs to know the identity of the current user.
In our current DI setup we get this from ambient data when resolving (HttpContext.Current to be specific). I've never liked that much, but I'm not sure there's any way around that in old Asp.Net.

I've been trying to solve that issue with Pure.DI and have come up with 4(5) solutions, but none are to my satisfaction and one doesn't compile. So I'm writing now in the hope of some guidance or ideally pointers to some obvious solution that I've missed.

I'll detail the five solutions here:

Two of the solutions are partially implemented in my fork of this project, but neither have been finished. There's quite a lot of work to do yet and since I'm not really interested in maintaining my own fork, I'm not going to continue the work unless there is some chance of getting the changes merged.

The first api change introduces IConfiguration BindAtResolve<T>(); to IConfiguration. The idea is that T will be added as an argument to the Resolve methods generated and those arguments being used to resolve types of T. I've yet to figure out if adding a new property for these in ResolverMetadata or adding them to ResolverMetadata.Bindings and adding a new binder or something else is the right way to do this.
This change is backwards-compatible and should work well with DependsOn.

The other api change adds generic argument TContextArgs to Setup and adds TContextArgs Args {get;} to IContext. This change requires IConfiguration, IBinding etc. to become generic on TContextArgs. An non-generic overload Setup() => Setup<Unit>() is provided to avoid breaking changes.
The generated Resolve methods will take TContextArgs as first argument unless it is Unit and that argument will then be available in the ctx argument in To<> methods.
This change will not play nice with DependsOn unless TContextArg is the same for all dependencies.
When I started work on this I hadn't understood that ctx mostly (only) acts as a placeholder and isn't actually available at runtime, so it's possible that it's never going to work.

Three of the solutions are using the project as-is and are reproduced in the following:

using System;
using Pure.DI;

namespace AppStart.Pure_DI;


public struct Identity {
    public int UserId;
    // More fields
}

public interface IIdentityService {
    Identity Identity { get; }
}

public class IdentityService: IIdentityService {
    public IdentityService(Identity identity) {
        Identity=identity;
    }

    public Identity Identity { get; }
}


internal class SomeService {
    public SomeService(IIdentityService identityService) {
        IdentityService=identityService;
    }

    public IIdentityService IdentityService { get; }
}

public static class Test {
    public static void Run() {
        Run(new Identity { UserId = 42 });
    }

    public static void Run(Identity ident) {
        // This fails at compile-time. See details in bindings
        SomeService s1 = ComposerX.Resolve<Func<Identity, WithIdentityResolver>>()(ident).Resolve<SomeService>();


        // This fails at run-time. See details in bindings
        SomeService s2 = ComposerX.Resolve<Func<Identity, GenericCompositionRootWithIdentity<SomeService>>>()(ident).Value;

        SomeService s3 = ComposerX.Resolve<Func<Identity, SpecializedCompositionRootWithIdentity>>()(ident).SomeService;
    }
}


class WithIdentityResolver {
    public WithIdentityResolver(IContext resolverContext) {
        ResolverContext=resolverContext;
    }

    public IContext ResolverContext { get; }

    public T Resolve<T>() {
        return ResolverContext.Resolve<T>();
    }
}


internal class GenericCompositionRootWithIdentity<T> {
    public GenericCompositionRootWithIdentity(T value) {
        Value=value;
    }

    public T Value { get; }
}


internal class SpecializedCompositionRootWithIdentity {
    public SpecializedCompositionRootWithIdentity(SomeService someService) {
        SomeService=someService;
    }

    public SomeService SomeService { get; }
}


static partial class ComposerX {

    class CachedService<TService> {
        public TService Service { get; set; }
    }

    static ComposerX() =>
        // out=C:\tmp
        // verbosity=Diagnostic
        DI.Setup()
            .Default(Lifetime.PerResolve)
            .Bind<CachedService<TT>>().To<CachedService<TT>>()
            .Bind<Func<Identity, IdentityService>>().To(ctx => new Func<Identity, IdentityService>(ident =>
                ctx.Resolve<CachedService<IdentityService>>().Service = new(ident)
            ))
            .Bind<IIdentityService>().To(ctx => ctx.Resolve<CachedService<IdentityService>>().Service)
            .Bind<SomeService>().To<SomeService>()

            // Doesn't work because Funcs with generic return values can't be found
            // Cannot resolve an instance System.Func`2[AppStart.Pure_DI.Identity,AppStart.Pure_DI.GenericCompositionRootWithIdentity`1[AppStart.Pure_DI.SomeService]], consider adding it to the DI setup.
            .Bind<Func<Identity, GenericCompositionRootWithIdentity<TT>>>().To(ctx =>
                new Func<Identity, GenericCompositionRootWithIdentity<TT>>(ident => {
                    // This Func resolve ensures that IdentityService is cached for the current resolve
                    ctx.Resolve<Func<Identity, IdentityService>>()(ident);
                    return new(ctx.Resolve<TT>());
                }))

            // Doesn't work because resolves on ctx are rebound and ctx itself is not available in the rewritten Func
            //  error CS0103: The name 'ctx' does not exist in the current context
            //[System.Runtime.CompilerServices.MethodImplAttribute((System.Runtime.CompilerServices.MethodImplOptions)768)]private static System.Func<AppStart.Pure_DI.Identity, AppStart.Pure_DI.WithIdentityResolver> GetPerResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIWithIdentityResolver(){if( _perResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIWithIdentityResolver==default(System.Func<AppStart.Pure_DI.Identity, AppStart.Pure_DI.WithIdentityResolver>)){ _perResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIWithIdentityResolver=                new Func<Identity,WithIdentityResolver>(ident => {
            //( GetPerResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIIdentityService())(ident);
            //                    return new(ctx);
            //            });
            .Bind<Func<Identity, WithIdentityResolver>>().To(ctx =>
                new Func<Identity, WithIdentityResolver>(ident => {
                    // This Func resolve ensures that IdentityService is cached for the current resolve
                    ctx.Resolve<Func<Identity, IdentityService>>()(ident);
                    return new WithIdentityResolver(ctx);
                }))

            // Does work but will cause binding bloat
            .Bind<Func<Identity, SpecializedCompositionRootWithIdentity>>().To(ctx =>
                new Func<Identity, SpecializedCompositionRootWithIdentity>(ident => {
                    // This Func resolve ensures that IdentityService is cached for the current resolve
                    ctx.Resolve<Func<Identity, IdentityService>>()(ident);
                    return new(ctx.Resolve<SomeService>());
                }))


            ;
}

I'm not really sure where to go from here, so any helpful would be appriciated.

Cheers,
John

Splat benchmark

Discussed in #23

Originally posted by mysteryx93 February 27, 2023
Could you add Splat to the benchmark? Requires Splat.DI.SourceGenerator

Pure.DI and Splat actually look pretty similar, I'm curious as to the difference between the two.

Since both are source generators, performance should be pretty close. Any feature that one has over the other?

Confusing error if parentheses are used when registering a factory method

Hi there,

I just spent a good half an hour trying to figure out what I was doing wrong registering a factory method

.Bind<ILoggerFactory>().As(Lifetime.Singleton).To((ctx) =>
{
    ctx.Inject(out ILogger logger);
    return new LoggerFactory()
        .AddSerilog(logger);
})

This code does not compile with the error

Error	DIE003	
The DI.Setup(nameof(CompositionRoot))
        .Bind<ILoggerFactory>().As(Lifetime.Singleton).To((ctx) =>
        {
            ctx.Inject(out ILogger logger);
            return new LoggerFactory()
                .AddSerilog(logger);
        }) is not supported.

After a bit of playing around, it seems that the problem is actually the parenthesis (ctx) => { } in the registration, as they interfere with the source generation and removing them allows the source generator to continue correctly.

Admittedly they're completely superfluous, but I honestly didn't even notice they were there in the sea of red squiggles in Visual Studio.

Environment information:
.net 8.0.101
C# LangVersion default (11)
Pure.DI version 2.0.45 (latest as of this report)

Rename project

I've been learning about this project and I'm excited to try it out. I think it has great potential. Unfortunately, the name is pretty bad. It's not imaginative nor is it searchable and discovery is key. Even removing the dot and using PureDI makes it more searchable.

Some other alternatives might be:

  • PurDI (maybe pronounced purr-dee or pure-dee)
  • DIGen
  • InjectGen
  • Injectinator

Keep up the good work!

Saving files with Code Cleanup with Pure.DI - performance issue

Hello, I have a large Winforms project with about 500 DI classes. When I save files, Visual Studio stuck on (for 50-500s):
pure00
In VS 2022 (17.5.1) I have enabled Options -> Text Editor -> Codde Cleanup -> Run Code Cleanup profile on Save. In profile, I have only: Remove unnecessary Imports.

Using the second VS instance I attached it to the first VS instance and dump active Tasks:
pure01

It looks like, Pure.DI unnecessarily runs during CodeCleanup. When I uninstall Pure.DI Code Cleanup runs very fast.

PureDI logs show only:
WRN Tracer: The path is empty.

How can I help you solve this problem? I was able to successfully migrate my entire project to PureDI. Unfortunately, with such performance in VS it is impossible to work ...

Generated Code emits Warnings from StyleCop rules

Sadly the generated code hits quite a few warnings in our codebase

Currently you deactivate the warnings from analyzers and JetBrains one by one by hand:

Instead you should make it clear to the Analyzers that the code is auto-generated and therefore the style is not checked:

https://shishkin.wordpress.com/2008/07/08/stylecop-how-to-ignore-generated-code/

You can add the comment on the start of the file:

// <auto-generated/>

I am not sure how necessary the file type is, but generated code usually has one of the following file endings, this could also help top avoid warnings from automatic tooling:

*.generated.cs
*.g.cs
*.Designer.cs

Lazy cyclic dependencies are no longer possible in v2.0

Description

Since v2.0, it is no longer possible to have a cyclic dependency where one of the dependencies is lazy resolved.
this results in the following error:

error DIE001: A cyclic dependency has been found Pure.DI.Example.Dependency <-- System.Lazy<Pure.DI.Example.IDependency2> <-- System.Func<Pure.DI.Example.IDependency2> <-- Pure.DI.Example.Dependency2 <-- Pure.DI.Example.Dependency.

In v1, code was generated to resolve the lazy dependency lazily.

Example

public class Dependency : IDependency
{
    public Dependency(Lazy<IDependency2> dependency2) { }
}

public class Dependency2 : IDependency2
{
    public Dependency2(IDependency dependency) { }
}

public partial class Composition
{
    public static void Setup() =>
        DI.Setup(nameof(Composition))
            .Bind<IDependency>().To<Dependency>()
            .Bind<IDependency2>().To<Dependency2>()
            .Bind<IService>().To<Service>().Root<IService>("Root");
}

Repository

https://github.com/Yves-Be/Pure.DI.LazyCircularDependency

Generic type composition root

For example:

interface IDependency;

class Dependency<T> : IDependency<T>;

interface IService<T>;

class Service<T>(IDependency<T> dependency) : IService<T>;

DI.Setup(nameof(Composition))
  .Bind<IDependency<TT>>().To<Dependency<TT>>()
  .Bind<IService<TT>>().To<Service<TT>>()
  .Root<IService<TT>>("MyRoot");

var composition = new Composition();
var root = composition.MyRoot<int>();

Add option to not generate `IServiceCollection` method

One use case is for MAUI app, they use MEDI but I only use Pure.DI, but Pure.DI still add the method for IServiceCollection even though it's not needed at least in my use case because the app has reference to MEDI

IsRegistered(Type type) check

The current asp.net integration seems not to give compile-time check advantage over the default asp container because it works based on OnCannotResolve hint which disables compile-time checks. I'd use another approach where you combine pure and asp containers instead of integration:

class CustomServiceProvider : IServiceProvider 
{
    private readonly Composition _composition;
    private readonly IServiceProvider _aspContainer;

    public object? GetService(Type type)
    {
        return _composition.HasRegistrationFor(type) ? _composition.Resolve(type) : _aspContainer.GetService();
    }
}

Then the pure container could be without any hints or extensions preventing from compile checks. You just need to take care of proper dependencies' disposal. However, I don't see a way to do that without catching an exception which is super slow. I'd suggest extending Pure.DI with such a possibility. It might be a registration check or Resolve method that doesn't throw implemented by a new hint or so.

Please think of this suggestion. In case you like/okay with the proposal, I can try to contribute if you give implementation details (method/hint names, etc).

warning CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

The generated code has invalid XML.

The summary tag starts outside the preprozessor if, and ends inside the preprozessor. I think everything should be inside the preprocessor.

/// <summary>
#if !NET35 && !NET20
    /// Represents the generic type arguments marker for <c>System.IDisposable</c>.
    /// </summary>
    [GenericTypeArgument]
    internal interface TTDisposable: System.IDisposable { }
#endif

SyntaxTree is not part of the compilation (Parameter 'syntaxTree') error in .NET SDK 7.0.304

Microsoft.CSharp.Core.targets(80,5): error : Unhandled exception. System.ArgumentException: SyntaxTree is not part of the compilation (Parameter 'syntaxTree') Microsoft.CSharp.Core.targets(80,5): error : at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSemanticModel(SyntaxTree syntaxTree, Boolean ignoreAccessibilit y) Microsoft.CSharp.Core.targets(80,5): error : at Microsoft.CodeAnalysis.Diagnostics.SuppressMessageAttributeState.IsDiagnosticSuppressed(Diagnostic diagnostic, Sup pressMessageInfo& info) Microsoft.CSharp.Core.targets(80,5): error : at Microsoft.CodeAnalysis.Diagnostics.SuppressMessageAttributeState.ApplySourceSuppressions(Diagnostic diagnostic)

System.InsufficientExecutionStackException: Insufficient stack to continue executing the program safely. This can happen from having too many functions on the call stack or function on the stack using too much stack space.

Hello,

I would like to migrate my big Winforms project from SimpleInjector to Pure.DI. During migration, if I have more than 100 bindings I get this error:

Warning	CS8785	Generator 'SourceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'HandledException' with message 'System.InsufficientExecutionStackException: Insufficient stack to continue executing the program safely. This can happen from having too many functions on the call stack or function on the stack using too much stack space.    at System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.LocalBinderFactory.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.LocalBinderFactory.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.LocalBinderFactory.VisitInvocationExpression(InvocationExpressionSyntax node)    at 

alker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Pure.DI.Core.MetadataWalker.VisitInvocationExpression(InvocationExpressionSyntax node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.DefaultVisit(SyntaxNode node)    at Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker.Visit(SyntaxNode node)    at Pure.DI.Core.MetadataBuilder.<GetMetadata>d__7.MoveNext()    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)    at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)    at Pure.DI.Core.MetadataBuilder.Build(IExecutionContext executionContext)    at Pure.DI.Core.SourceBuilder.Build(IExecutionContext executionContext)    at Pure.DI.Core.Generator.Generate(IExecutionContext context)'	XXX C:\xx\xx.csproj	1	Active

If you need more information, please ask.

Singletons

Hi,

When specifying singletons, it appears, from the generated code, that most of the Resolve() methods will retrieve the generated static internal readonly [T] Shared field from the generated Singleton[T] class; as you'd expect.

However, the generated Resolve[T]() methods will create a new instance of the type T. (I've used [T] here to indicate a direct substitution so as to not confuse with generics, etc.)

Using the ShroedingersCat sample, but using the following Setup:

        private static void Setup() => DI.Setup()
            .Default(Singleton) // Use singletons
            // Represents a quantum superposition of 2 states: Alive or Dead
            .Bind<State>().To(_ => (State)Indeterminacy.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
            .Bind<Program>().To<Program>();

The resulting ResolveSampleProgram is generated as:

        [System.Runtime.CompilerServices.MethodImplAttribute((System.Runtime.CompilerServices.MethodImplOptions)256)]
        public static Sample.Program ResolveProgram()
        {
            return // Bind<Sample.Program>().As(Singleton).To<Sample.Program>()
            new Sample.Program(...);
        }

When I'd expect it to be:

[System.Runtime.CompilerServices.MethodImplAttribute((System.Runtime.CompilerServices.MethodImplOptions)256)]
        public static Sample.Program ResolveProgram()
        {
            return // Bind<Sample.Program>().As(Singleton).To<Sample.Program>()
            SingletonSampleProgram.Shared;
        }

Indeed the SingletonSampleProgram.Shared is generated and used in the ResolversTable, but this final step creates a new instance.

Having specified a singleton pattern, I wouldn't expect instances to be 'leaked' in this way?

Class library usage example

Hi,

Nice project. I've read through and I'm not clear on one quite common use case.

Let's say I'm creating a class library that wants to expose a utility method to inject dependencies into a container, how would you recommend achieving that?

For example, my library might expose a number of interfaces with concrete implementations, and want to expose an extension method like:

    public static class DIExtensions
    {
        public static IConfiguration UsingLib(this IConfiguration configuration) => configuration
            .Bind<IA>.To<A>()
            .Bind<IB>.To<B>();
    }

Which you would use like:

        private static void Setup() => DI.Setup()
			.UsingLib()
			.Bind...

However IConfiguration is internal, so you can't even do this kind of thing even in the same project.

I understand that the point of DI is that the classes (in my example 'A' and 'B') don't need to know about each other, and that Pure.DI "isn't a framework", however, it is often useful to have a chunk of 'common usage' code for convenience to prevent a lot of boiler-plate code needing to be added each time.

Disposable Instances Handling

Hi there,

I have a question regarding the disposable instances created by the DI. I've noticed that these disposable instances are only disposed automatically if their lifetime is set to singleton. Is this behavior intended or is it a bug?

Additionally, I've been exploring workarounds and came across this readme, but I'm unsure where to integrate the following code:

// Disposal of instances in reverse order
while (disposables.TryPop(out var disposable))
{
    disposable.Dispose();
}

Any guidance on where to incorporate this code would be greatly appreciated.

Thank you in advance for your assistance!

Performance of the source generator.

Hello!

I'm not sure about these topics, so I post them here only fyi.

  1. If I understand the things right, then https://github.com/DevTeam/Pure.DI/blob/master/src/Pure.DI/SourceGenerator.cs#L27 means you are collecting all syntax nodes inside a whole source tree of the project (solution?). Looks like it will have scaling problems. What are performance of this approach? Is there any performance benefits in comparison with old ISourceGenerator?
  2. Again, If I not missed something, pushing GeneratorSyntaxContext into generator pipeline https://github.com/DevTeam/Pure.DI/blob/master/src/Pure.DI/SourceGenerator.cs#L28 renders the generator to be completely non incremental. Additional context can be found here: zompinc/sync-method-generator#20

As an author of similar library https://github.com/lsoft/DpDtInject I found no way to effiently apply incremental source generators for the task of DI. If I wrong, could you share where did I miss?

Good luck! Thanks!

Resolving transient dependencies with MS DI is really slow

If I use Pure.DI to inject and resolve dependencies, then it is really quick.
If I use MS DI to inject and resolve dependencies, then it is faster.
If I use inject with Pure.DI and resolve with MS DI, it is really slow.

Method Mean Error StdDev Ratio RatioSD
PureDI 5.278 ns 0.1770 ns 0.2857 ns 1.00 0.00
DotNet 45.698 ns 0.9411 ns 1.0837 ns 8.67 0.58
PureDIResolvedWithMS 71.330 ns 0.9999 ns 0.9353 ns 13.60 0.86

I would have expected injecting with Pure.DI and resolving with MS DI to be somewhere in the middle, not significantly worse. Do you have any Ideas on what is happening?

Code:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.Extensions.DependencyInjection;
using Pure.DI;

var summary = BenchmarkRunner.Run<DIComparison>();

public class DIComparison
{
    private IServiceProvider _microsoftServiceProvider;
    private IServiceProvider _pureDIAndMSServiceProvider;
    private IServiceProvider _handCraftedServiceProvider;

    [GlobalSetup]
    public void Setup()
    {
        _microsoftServiceProvider = new ServiceCollection()
            .AddTransient<Something>()
            .AddTransient<SomethingElse>()
            .BuildServiceProvider();

        _pureDIAndMSServiceProvider = new ServiceCollection()
            .AddPureDIClass()
            .BuildServiceProvider();
    }

    [Benchmark(Baseline = true)]
    public Something PureDI() => PureDIClass.Resolve<Something>();

    [Benchmark(Baseline = false)]
    public Something DotNet() => _microsoftServiceProvider.GetRequiredService<Something>();

    [Benchmark]
    public Something PureDIResolvedWithMS() => _pureDIAndMSServiceProvider.GetRequiredService<Something>();
}

public class Something 
{
    public Something(SomethingElse somethingElse) { }
}

public class SomethingElse { }

public static partial class PureDIClass
{
    public static void Setup() => DI.Setup()
        .Bind<Something>().As(Lifetime.Transient).To<Something>()
        .Bind<SomethingElse>().As(Lifetime.Transient).To<SomethingElse>();
}

Asp.Net Example

Firstly, been watching this repo to see what 2.0 was going to bring and excited to see the possibilities! Awesome work.

Second, I didn't see it in the list in the readme, but do you have an example for how this might be used in an ASP.Net 6 project. This seems like a big chunk of what would need to happen to make it AoT compatible (plus the stuff that's coming in .Net 8).

DIE999: An unhandled error has occurred. Collection was modified. (Visual Studio)

Hi,
I'm trying to build the Pure.DI.Example project in Visual Studio 2022,
but I'm getting the following error when the code generation runs:

Generator....................... DIE999: An unhandled error has occurred.
Collection was modified; enumeration operation may not execute.

I've tested several versions (2.0.7, 2.0.10, and 2.0.11-beta2), but they all seem to have the same issue.
A dotnet build does seem to work, so it might be related to Visual Studio.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.