Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/lsoft/DpdtInject
Highly efficient compile-time general purpose DI container based on C# source generators.
https://github.com/lsoft/DpdtInject
csharp csharp-sourcegenerator dependency-injection di-container di-framework inversion-of-control ioc performance source-generators sourcegenerator visual-studio-extension vsix-extensions
Last synced: 3 months ago
JSON representation
Highly efficient compile-time general purpose DI container based on C# source generators.
- Host: GitHub
- URL: https://github.com/lsoft/DpdtInject
- Owner: lsoft
- License: mit
- Created: 2020-09-15T11:14:28.000Z (about 4 years ago)
- Default Branch: master
- Last Pushed: 2022-06-23T04:32:04.000Z (over 2 years ago)
- Last Synced: 2024-04-27T00:37:01.297Z (7 months ago)
- Topics: csharp, csharp-sourcegenerator, dependency-injection, di-container, di-framework, inversion-of-control, ioc, performance, source-generators, sourcegenerator, visual-studio-extension, vsix-extensions
- Language: C#
- Homepage: https://www.nuget.org/packages/Dpdt.Injector/
- Size: 4.12 MB
- Stars: 32
- Watchers: 2
- Forks: 3
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- csharp-source-generators - DpdtInject - ![stars](https://img.shields.io/github/stars/lsoft/DpdtInject?style=flat-square&cacheSeconds=604800) ![last commit](https://img.shields.io/github/last-commit/lsoft/DpdtInject?style=flat-square&cacheSeconds=86400) DI container based on C# Source Generators. Its goal is to remove everything possible from runtime and make resolving process as faster as we can. This is achieved by transferring huge piece of resolving logic to the compilation stage into the source generator. (Demo, PoC and excercise projects / Other)
- awesome-roslyn - DpDtInject - Proof-of-concept of a dependency injection container that transfers huge piece of resolving logic to the compilation stage. Offers additional compile-time safety and fast runtime resolution. (Source Generators)
README
# DpdtInject
![Dpdt logo](logo.png)
[![Compilation Status](https://github.com/lsoft/DpdtInject/actions/workflows/dpdt.yml/badge.svg)](https://github.com/lsoft/DpdtInject/actions)
[![Nuget](https://buildstats.info/nuget/Dpdt.Injector?includePreReleases=true)](https://www.nuget.org/packages/Dpdt.Injector/)## Table Of Contents
- [Purpose](#purpose)
- [Status](#status)
- [Design features](#design-features)
- [Other features](#other-features)
- [Performance](#performance)
- [How to try](#how-to-try)
- [Design](#design)
* [To be fair: design drawbacks at first place](#to-be-fair-design-drawbacks-at-first-place)
* [Cluster](#cluster)
* [Cluster life cycle](#cluster-life-cycle)
* [Child clusters](#child-clusters)
* [Syntax](#syntax)
+ [Regular singleton/transient/custom binding](#regular-singletontransientcustom-binding)
+ [Regular const binding](#regular-const-binding)
+ [Conditional bindings](#conditional-bindings)
+ [Predefined constructor arguments with additional settings](#predefined-constructor-arguments-with-additional-settings)
+ [Proxy (and decorator at the same time) bindings](#proxy-and-decorator-at-the-same-time-bindings)
- [What is saver?](#what-is-saver)
+ [Conventional bindings](#conventional-bindings)
* [Choosing constructor](#choosing-constructor)
* [Scope](#scope)
+ [Singleton](#singleton)
+ [Transient](#transient)
+ [Constant](#constant)
+ [Custom](#custom)
* [Conditional binding](#conditional-binding)
* [Fast resolutions](#fast-resolutions)
* [Compile-time checks](#compile-time-checks)
+ [Did source generators are finished their job?](#did-source-generators-are-finished-their-job)
+ [Unknown constructor argument](#unknown-constructor-argument)
+ [Singleton takes transient or custom](#singleton-takes-transient-or-custom)
+ [Circular dependencies](#circular-dependencies)
+ [More than 1 unconditional child](#more-than-1-unconditional-child)
+ [Conventional bindings](#conventional-bindings-1)
* [Async resolutions](#async-resolutions)
* [Settings](#settings)
+ [Cross cluster resolutions](#cross-cluster-resolutions)
- [OnlyLocalCluster](#onlylocalcluster)
- [AllowedCrossCluster](#allowedcrosscluster)
- [MustBeCrossCluster](#mustbecrosscluster)
+ [Wrapper producing](#wrapper-producing)
- [NoWrappers](#nowrappers)
- [ProduceWrappers](#producewrappers)
+ [Circular checking](#circular-checking)
- [PerformCircularCheck](#performcircularcheck)
- [SuppressCircularCheck](#suppresscircularcheck)
+ [Constructor choosing](#constructor-choosing)
- [AllAndOrderConstructorSetting](#allandorderconstructorsetting)
- [SubsetAndOrderConstructorSetting](#subsetandorderconstructorsetting)
- [SubsetNoOrderConstructorSetting](#subsetnoorderconstructorsetting)
* [Debugging your clusters and conditional clauses](#debugging-your-clusters-and-conditional-clauses)
* [Artifact folder](#artifact-folder)
- [Dpdt Visual Studio Extension](#dpdt-visual-studio-extension)
- [Known problems](#known-problems)
- [Alternatives](#alternatives)
- [Feedback](#feedback)# Purpose
Dpdt is a compile-time general purpose DI container based on C# Source Generators. Its goal is to remove everything possible from runtime and make resolving process as faster as we can. This is achieved by transferring huge piece of resolving logic to the compilation stage into the source generator.
As an additional concept, Dpdt adds no references to your distribution, so if you are developing a library/nuget package, you are free to use Dpdt as DI container. You will not impose your DI to your users, everything will builtin in your dlls.
# Status
There is a meduim size project powered by Dpdt that runs in its production. So, I think Dpdt is in Alpha status now.
# Design features
0. As mentioned above, Dpdt suitable for lib/nuget developers. This **does not mean** that Dpdt is not (or less) suitable for applications.
0. [Dpdt Visual Studio Extension](https://github.com/lsoft/DpdtInject#dpdt-visual-studio-extension) helps you to be more productive.
0. [Additional compile-time checks](https://github.com/lsoft/DpdtInject#compile-time-checks).
0. No performance decrease on the platforms with no compilation at runtime (because of absense runtime compilation!).# Other features
More to come!
# Performance
0. Very impressive Fast resolutions.
0. Good Generic resolution performance.
0. Good enough, but not the best NonGeneric resolution - Microresolver is fantastically fast; what's the magic? :)``` ini
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores
.NET Core SDK=5.0.100-rc.2.20479.15
[Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
Job-OKTIPR : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJITRuntime=.NET Core 3.1 Server=True
```
Here is the results of a resolution a complex tree of 500 objects total:
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------------------------------- |----------:|----------:|----------:|------:|------:|------:|----------:|
| Dpdt.GenericSingleton500 | 4.325 us | 0.0407 us | 0.0381 us | - | - | - | - |
| Dpdt.NonGenericSingleton500 | 13.641 us | 0.1126 us | 0.1054 us | - | - | - | - |
| Dpdt.FastSingleton500 (**fastest**) | 2.577 us | 0.0104 us | 0.0097 us | - | - | - | - |
| DryIoc.GenericSingleton500 | 16.148 us | 0.1470 us | 0.1375 us | - | - | - | - |
| DryIoc.NonGenericSingleton500 | 9.728 us | 0.1689 us | 0.1580 us | - | - | - | - |
| Microresolver.GenericSingleton500 | 7.276 us | 0.0573 us | 0.0536 us | - | - | - | - |
| Microresolver.NonGenericSingleton500 | 3.784 us | 0.0158 us | 0.0148 us | - | - | - | - || Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------------------------------- |---------:|---------:|---------:|-------:|------:|------:|----------:|
| Dpdt.GenericTransient500 | 40.32 us | 0.330 us | 0.309 us | 4.1504 | - | - | 77.02 KB |
| Dpdt.NonGenericTransient500 | 63.08 us | 0.489 us | 0.433 us | 4.1504 | - | - | 77.02 KB |
| Dpdt.FastTransient500 (**fastest**) | 37.93 us | 0.253 us | 0.224 us | 3.8452 | - | - | 71.2 KB |
| DryIoc.GenericTransient500 | 66.87 us | 1.288 us | 1.581 us | 4.2725 | - | - | 77.02 KB |
| DryIoc.NonGenericTransient500 | 62.60 us | 0.480 us | 0.449 us | 4.1504 | - | - | 77.02 KB |
| Microresolver.GenericTransient500 | 64.77 us | 0.294 us | 0.261 us | 4.2725 | - | - | 77.02 KB |
| Microresolver.NonGenericTransient500 | 68.52 us | 2.869 us | 8.415 us | 4.1504 | - | - | 77.02 KB |
Also I recommend disable tiered compilation for composition root assembly if you want to obtain full performance at the start.
# How to try
## Without Dpdt Visual Studio Extension
- Create new Console Application in Visual Studio or `dotnet` console command. Keep in mind you need to set `net5` or `net6` target framework.
- Install latest `Dpdt.Injector` [nuget package](https://www.nuget.org/packages/Dpdt.Injector/). Keep in mind that nuget may be in the prerelease state.
- (optional) You can disable tiered compilation for composition root assembly and set `EmitCompilerGeneratedFiles` to `true`.
- At this point, you have something like the following in your csproj file:```xml
Exe
net5
false
false
falsetrue
```
- Next, create a class which will be resolved from a Dpdt container, for example: `public class MyPayload { }`
- You will need a Dpdt cluster class:```csharp
public partial class MyCluster : DpdtInject.Injector.Src.DefaultCluster
{
[DpdtInject.Injector.Src.DpdtBindingMethod]
public void Bind()
{
Bind()
.To()
.WithSingletonScope()
;
}
}
```- Now, it's time to create a cluster and take our payload from it; put the following at `Program.Main`:
```csharp
/*await/* var cluster = new MyCluster(null); //await is needed to dispose singletons with IAsyncDisposable, but without IDisposable interfaces
var payload = cluster.Get();
Console.WriteLine(payload.GetType().Name);
```- Finally, run your program.
## With Dpdt Visual Studio Extension
- Install Dpdt Visual Studio Extension for [Visual Studio 2019](https://marketplace.visualstudio.com/items?itemName=lsoft.DpdtVisualStudioExtension) or for [Visual Studio 2022](https://marketplace.visualstudio.com/items?itemName=lsoft.DpdtVisualStudioExtension2022).
- Restart Visual Studio.
- Create new Console Application in Visual Studio. Keep in mind you need to set `net5` or `net6` target framework.
- (optional) You can disable tiered compilation for composition root assembly and set `EmitCompilerGeneratedFiles` to `true`.
- Install the latest Dpdt Nuget Package via [context menu](extension4.png)
- Create Dpdt cluster and binding method via [context menu](extension4.png) and the [tool window](extension5.png)
- Next, create a class which will be resolved from a Dpdt container, for example: `public class MyPayload { }`
- Add the binding clause to the `Bind` method of the produced cluster:```csharp
Bind()
.To()
.WithSingletonScope()
;
```- Now, it's time to create a cluster and take our payload from it; put the following at `Program.Main`:
```csharp
/*await*/ using var cluster = new MyCluster(null); //await is needed to dispose singletons with IAsyncDisposable, but without IDisposable interfaces
var payload = cluster.Get();
Console.WriteLine(payload.GetType().Name);
```- Finally, run your program.
# Design
## To be fair: design drawbacks at first place
0. Because of design, it's impossible to `Unbind` and `Rebind`.
0. Because of source generators, it's impossible to direclty debug your bind code, including its `When` predicates.
0. Because of massive rewriting the body of the cluster, it's impossible to use a local variables (local methods and other local stuff) in `ConstructorArgument` and `When` predicates. To make bind works use instance based fields, properties and methods instead. To make bind debuggable, use fields, properties and methods of the other, helper class.
0. No deferred bindings by design with exception of cluster hierarchy.
0. Slower source-to-IL compilation, slower JIT compilation.## Cluster
Dpdt bingings organized in the groups named `clusters`. Cluster is a class that derived from Dpdt's `DpdtInject.Injector.Src.DefaultCluster`. This class should be `partial`. Each cluster may have any numbers of binding methods even in different compilation units. These methods should be marked with attribute `[DpdtBindingMethod]`. No argument allowed for that methods, and in fact they are not executed at all. You can use this to split your bindings into different groups (something like Ninject's modules).
## Cluster life cycle
The life cycle of the cluster begins by creating it with `new`. The cluster can take other cluster as its parent, so each unknown dependency will be resolved from the parent (if parent exists, otherwise exception would be thrown).
The end of the life cycle of a cluster occurs after the call to its `Dispose` method. At this point, all of its disposable singleton bindings are also being disposed. It is prohibited to dispose of the cluster and use it for resolving in parallel . It is forbidden to resolve after a `Dispose`.
If you have at least one singleton with `IAsyncDisposable` interface, but without `IDisposable` interface, you need to invoke `DisposeAsync` instead of `Dispose` at the cluster object. The rule is simple: `Dispose` cluster -> `Dispose` its singletons, `AsyncDispose` cluster -> `Dispose` + `DisposeAsync` its singletons. So, I recommend always use `DisposeAsync` for a cluster objects. **Disposing order is undefined.**
## Child clusters
```csharp
public partial class RootCluster : DefaultCluster
{
public RootCluster(your arguments) : this((ICluster)null!) { ... }
}public partial class ChildCluster : DefaultCluster
{
public ChildCluster(ICluster cluster, your arguments) : this(cluster) { ... }
}...
/*await*/ using var rootCluster = new RootCluster(
your arguments
);
/*await*/ using var childCluster = new ChildCluster(
rootCluster,
your arguments
);
```Clusters are organized into a tree. This tree cannot have a circular dependency, since it is based on constructor argument. Dependencies, consumed by the binding in the child cluster, are resolved from the home cluster if exists, if not - from **parent cluster**.
If some binging does not exist in local cluster, Dpdt will request it from parent cluster at runtime. This behavior can be modified by settings `OnlyLocalCluster`/`AllowedCrossCluster`/`MustBeCrossCluster`.
Child clusters must be disposed BEFORE its parent.
## Syntax
Dpdt syntax was partially inspired by Ninject. A lot of examples of allowed syntaxes are available in the test project. Please refer that code.
### Regular singleton/transient/custom binding
```csharp
Bind()
.To()
.WithSingletonScope() //WithTransientScope, WithCustomScope
;
```### Regular const binding
Only readonly fields, static readonly fields and in-place compile-time constants are allowed to be a target constant:
```csharp
private /*static*/ readonly string _roString = "readonly string";...
Bind()
.WithConstScope(_roString)
;Bind()
.WithConstScope("some inplace string")
;```
### Conditional bindings
```csharp
Bind()
.WithConstScope(ConstantA)
.When(rt => rt.ParentTarget?.TargetType == typeof(B2))
;
```### Predefined constructor arguments with additional settings
```csharp
Bind()
.To()
.WithSingletonScope()
.Setup()
.Setup>() //imagine that argument1 is int, and argument2 is long; note: you are NOT forced to use constructor setting if you are using ConstructorArgument
.Configure(new ConstructorArgument("argument1", field_or_property_or_expression1))
.Configure(new ConstructorArgument("argument2", field_or_property_or_expression2))
;
```### Proxy (and decorator at the same time) bindings
```csharp
//your custom telemetry event saver
//saver is invoked from multiple threads even in parallel
//so saver must be thread-safe
Bind()
.To()
.WithSingletonScope()
;//example of the payload, that will be injected into the proxy:
Bind()
.To()
.WithSingletonScope() //may be singleton or transient
.When(rt => rt.WhenInjectedExactlyInto())
;//proxy binding example
//proxy invokes its saver for every invocation of the proxied methods, EVEN in parallel!
Bind()
.ToProxy()
.WithProxySettings() //additional details about these classes are available at the tests project
.WithSingletonScope() //proxy should be in the same scope as its payload
.Setup() //this suppress unused warning
.When(rt => rt.WhenInjectedExactlyNotInto()) //this suppress stack overflow during resolution
;//"proto" class of the proxy:
public partial class ProxyCalculator : ICalculator { }```
Dpdt proxy can decorate a methods, properties (get/set), events (add/remove) and indexers.
#### What is saver?
Saver is an interceptor class that is used by the Proxy-decorator to convey some info about the current invocation. Saver MUST be thread-safe!
Saver has 2 methods:
0. StartSessionSafely
0. FixSessionSafelySuffix `Safely` means that this method should not raise any exception; please wrap their bodies into `try`-`catch`.
`StartSessionSafely` is invoked at the beginning of decorated (proxied) method. Its arguments:
0. `fullClassName` contains a full class name (e.g. `Dpdt.Injector.DefaultCluster`)
0. `memberName` contains a method\property\event name, or `long this[]` for an indexer, where `long` is a indexer return type
0. `arguments` contains an array of member arguments; imagine a method `void DoSomething(int a, long b)`, in this case `arguments` will contain: name of argument a (e.g. string "a"), value of argument a (e.g. some int), name of argument b (e.g. string "b"), value of argument b (e.g. some long); if no arguments exists `arguments` may be `null`Please make note: parameters with `out` modifier will not appear in `arguments`.
`StartSessionSafely` returns a newly created `Guid`.
`FixSessionSafely` is invoked at the end of proxied-method. Its arguments:
0. `sessionGuid` contains a Guid that earlier has been returned from `StartSessionSafely`
0. `takenInSeconds` contains a time (a fraction of seconds) that has been spent in decorated member
0. `exception` contains an exception in the case if decorated member raises an exception, otherwise `null`In the wild, session class usually uses `ConcurrentDictionary` to store sessions in `StartSessionSafely`. In `FixSessionSafely` you can do `TryRemove`, append the timings and exception, and send this completed session to the logger or whatever you need/want.
### Conventional bindings
Conventional bindings are "machine-gun" binding producing machine, that scans your assemblies and produces a lot of binding statements. If you are know Ninject.Conventions you know what I wanted to implement. Conventional bindings works with Roslyn symbols and are produced at compile-time.
```csharp
ScanInAssembliesWith() //scan assembly(ies) that contains a specified type(s)
.SelectAllWith() //select all classes that implements a specific class/interface
.ExcludeAllWith() //but not to include all classes that implements this class/interface
.From() //binds from these interfaces/classes (they may be different with the classes/interfaces in SelectAllWith); others options are exists too
.ToItself() //to the processed class
.WithSingletonScope() //and now we continue with regular binding syntax, including With*Scope/Setup/Configure/When and etc.
;
```Example how to bind from open generic:
```csharp
//the following statement will bind A0 and A2; A1 will be excluded due to IExclude<>
ScanInAssembliesWith()
.SelectAllWithOpenGeneric>() //generic argument 'object' means NOTHING! it will be removed by Dpdt, so IA will be transformed into IA<> (open generic)
.ExcludeAllWithOpenGeneric>() //again, 'object' means nothing, as above
.FromAllInterfaces()
.ToItself()
.WithSingletonScope()
;public interface IA { }
public interface IA : IA { }
public interface IExclude { }
public class A0 : IA { }
public class A1 : IA, IExclude { }
public class A2 : IA { }
```Example how to exclude an item from a conventional binding. Imagine you have the following:
```csharp
//this statement will bind A0 A1 A2 classes
ScanInAssembliesWith()
.SelectAllWith()
.From()
.ToItself()
.WithSingletonScope()
;
```but you want to bind `A1` in a slightly different way. You can easily do that:
```csharp
//this statement will bind A0 A2 classes
ScanInAssembliesWith()
.SelectAllWith()
.ExcludeAllWith() //only append this
.From()
.ToItself()
.WithSingletonScope()
;//and add a new binding statement
Bind()
.To()
.WithTransientScope() //here is the difference: transient instead of singleton
;
```Dpdt does not support conventional bindings to proxy or factory, because of design.
## Choosing constructor
Constructor is chosen at the compilation stage based on 3 principles:
0. If constructor filtering setting is set, it will be applied against existing constructors.
0. If any `ConstructorArgument` filter are set, they will be applied against constructors filtered by previous step. If no `ConstructorArgument` has defined, all filtered constructors will be taken.
0. After all filtering, the constructor with the minimum number of parameters is selected to make a binding.## Scope
Bind clause with no defined scope raises a question: an author forgot set a scope or wanted a default scope? We make a decision not to have a default scope and force a user to define a scope.
### Singleton
The only one instance of defined type is created. If instance is `IDisposable` then `Dispose` method will be invoked at the moment the cluster is disposing. This operation is thread safety, double checking locking algorithm is on.
### Transient
Each resolution call results with new instance. `Dispose` for targets will not be invoked.
### Constant
Constant scope is a scope when the cluster receive an outside-created object. Its `Dispose` will not be invoked, because the cluster is not a parent of the constant object.
### Custom
```csharp
Bind()
.To()
.WithCustomScope()
;...
/*await/* using(var scope1 = cluster.CreateCustomScope())
{
var a1 = cluster.Get(scope1);/*await/* using(var scope2 = cluster.CreateCustomScope())
{
var a2 = cluster.Get(scope2);
} //here we dispose a2 if target for IA is IDisposable /*or IAsyncDisposable*/
} //here we dispose a1 if target for IA is IDisposable /*or IAsyncDisposable*/
````IDisposable` custom-binded objects will be disposed at the moment of the scope object dispose. DO NOT forget to invoke `Dispose` on scope object! Otherwise, Custom-scoped disposable objects will not be disposed too.
If you have at least one custom scoped binding with `IAsyncDisposable` interface, but without `IDisposable` interface, you need to invoke `DisposeAsync` instead of `Dispose` at the scope object. The rule is simple: `Dispose` scope object -> `Dispose` its bindings, `AsyncDispose` scope object -> `Dispose` + `DisposeAsync` its bindings. So, I recommend always use `DisposeAsync` for a scope objects. **Disposing order is undefined.**
Keep in mind, custom-scoped bindings are resolved much slower than singleton/transient/constant bindings.
## Conditional binding
Each bind clause may have an additional filter e.g.
```csharp
Bind()
.To()
.WithSingletonScope()
.When(IResolutionTarget rt =>
{
condition to resolve
})
;
```Please refer unit tests to see the examples. Please note, than any filter makes a resolution process slower (a much slower! 10x slower in compare of unconditional binding!), so use this feature responsibly. Resolution slowdown with conditional bindings has an effect even on those bindings that do not have conditions, but they directly or indirectly takes conditional binding as its dependency. Therefore, it is advisable to place conditions as close to the resolution root as possible.
Also, these predicates cannot be debugged because they are rewrited by Dpdt. See below how to overcome it.
## Fast resolutions
Dpdt contains a special resolution type named 'fast'. Its syntax is `cluster.GetFast(default(IMyInterface));`. In general this syntax is faster that generic resolutions, but it has one additional constraint: you need to resolve directly from cluster type, it is impossible to cast cluster to the one of its interface (like `ICluster` or `IResolution`) and do fast resolutions.
## Compile-time checks
Each safety checks are processed in the scope of concrete cluster. Dpdt cannot check for cross-cluster issues because clusters tree is built at runtime. But cross-cluster checks are performed in the cluster constructor at runtime.
### Did source generators are finished their job?
Dpdt adds a warning to compilation log with the information about how many clusters being processed and time taken. It's an useful bit of information for debugging purposes.
### Unknown constructor argument
Dpdt will break ongoing compilation if binding has useless `ConstructorArgument` clause (no constructor with this parameter exists).
### Singleton takes transient or custom (captive dependency)
Dpdt can detect cases of singleton binding takes a transient/custom binding as its dependency, and make signals to the programmer. It's not always a bug, but warning might be useful.
### Circular dependencies
Dpdt is available to determine circular dependencies in your dependency tree. In that cases it raise a compilation error. One additional point: if that circle contains a conditional binding, Dpdt can't determine if circular dependency will exists at runtime, so Dpdt raises a compile-time warning instead of error. This behaviour can be changed by appropriate setting.
### More than 1 unconditional child
If for some binding more than 1 unconditional child exists it renders parent unresolvable, so Dpdt will break the compilation is that case.
### Conventional bindings
Dpdt will write some info in Build log for every conventional binding produced. For regular binding - will not. It is useful for debugging conventional bindings results.
## Async resolutions
Dpdt is a constructor-based injector. Async resolutions are not supported because we have no an async constructors. Consider using a factory class with an async method `async Task CreateSomethingAsync(...)`.
## Settings
Settings are things that modify compile-time cluster code generation. THEY ARE NOT WORKING AT RUNTIME.
### Cross cluster resolutions
These settings relates with checking for child binding resolutions; they are useful for an additional safety. They are applied for each of child resolutions.
#### OnlyLocalCluster
Each dependency **must** exists in local cluster. If not - ongoing compilation will break. Note: binding conditions is out of scope, only existing matters. You can define a local binding with `When(rt => false)`, and this check will mute. So, this setting is not something that can protect you at 100%. This is a default behaiour.
#### AllowedCrossCluster
Any dependency may be in home cluster or parent cluster. If local dependecy found at runtime it is used, otherwise request to the parent cluster is performed. (this is default behaviour for old version of Dpdt)
#### MustBeCrossCluster
NO local dependency allowed, any dependency MUST be in the parent cluster. If local dependency found, ongoing compilation will break. Note: binding conditions is out of scope, only existing matters. You can define a local binding with `When(rt => false)`, and this check will fire. So, this setting is not something that can protect you at 100%.
### Wrapper producing
These settings relate with a producing of wrapped-resolutions, like `Func<>`; they are useful for minimizing the cluster size.
#### NoWrappers
No bindings for wrappers will be produced. It is a default value.
#### ProduceWrappers
**Every** type of wrappers will be produced for this binding.
### Circular checking
These settings relates with a circular checking; they are useful for removing unused noise from build log (for example in case of decorator, look at `ProxySimple0_Fixture` unit test).
#### PerformCircularCheck
Do circular checking. It is a default value.
#### SuppressCircularCheck
Do not circular checking. Use this for decorators bindings.
### Constructor choosing
These settings relates with a constructor choosing algorithm performed by Dpdt. These settings sets a constructor argument **types** filter.
#### AllAndOrderConstructorSetting
Dpdt will take the only constructor that have a parameters with the types selected in this setting and in the same order. For example `.Setup>()` means that only constructor `MyClass(int a, long b)` will choose (argument names does not matters).
#### SubsetAndOrderConstructorSetting
Dpdt will take the only constructors that have a parameters with the types selected in this setting and in the same order, and additional arguments may exists before or after the selected. For example `.Setup>()` will choose the following constructors `MyClass(..., int b, long c)` `MyClass(int a, long b)` `MyClass(int a, long b, ...)`.
#### SubsetNoOrderConstructorSetting
Dpdt will take the only constructors that have a parameters with the types selected in this setting and their order does not matters, any additional arguments may exists. For example `.Setup>()` will choose the following constructors `MyClass(long a, int b)` `MyClass(int a, long b)` `MyClass(..., int a, ... long b, ...)` `MyClass(..., long a, ..., int b, ...)`.
Please make note: `in` `readonly` and `ref` modifiers of the constructor arguments will not taken into account.
## Debugging your clusters and conditional clauses
Because of source generators are generating new code based on your code, it's impossible to direclty debug your cluster code, including its `When` predicates (because this code is not actually executed at runtime). It's a disadvantage of Dpdt design. For conditional clauses, you need to call another class to obtain an ability to catch a breakpoint:
```csharp
public partial class MyCluster : DefaultCluster
{
[DpdtBindingMethod]
public void BindMethod()
{
Bind()
.To()
.WithSingletonScope()
.When(rt =>
{
//here debugger is NOT workingreturn Debugger.Debug(rt);
})
;
}
}public static class Debugger
{
public static bool Debug(IResolutionTarget rt)
{
//here debugger is working
return true;
}
}
```## Artifact folder
As a regular source generator, Dpdt is able to store pregenerated C# code at the disk. The only thing you need is correctly setup your csproj. For example:
```xml
true
```If your clusters are huge, you may face with slowdowns in your work in VS because VS runs Dpdt in the background. To overcome this please put the following into your csproj:
```xml
false
```
This is to turn off code beautification so Dpdt will produce cluster code a bit faster.# Dpdt Visual Studio Extension
To make a dealing with Dpdt easier, a Visual Studio Extension has been developed. Make note: it only supports [Visual Studio 2019 16.8 (and later)](https://marketplace.visualstudio.com/items?itemName=lsoft.DpdtVisualStudioExtension) and [Visual Studio 2022](https://marketplace.visualstudio.com/items?itemName=lsoft.DpdtVisualStudioExtension2022).
Please make note: Dpdt Visual Studio extension should be the SAME version as your Dpdt.Injector package version. If it is not possible, please use the latest version of the both. I carefully try to publish compatible versions of the new nuget and new vsixes at the same time. Sometimes, a new version of vsix can be published without nuget, and the opposite. It means that the new version of vsix (or nuget) will be compatible with last existing version of the nuget (or vsix).
If you click on a project in Solution Explorer and there is no Dpdt nuget installed, you can install its latest version easily:
![Dpdt Extension Image 4](extension4.png)
Also, you can create a new cluster class or add a new binding method to the existing cluster:
![Dpdt Extension Image 5](extension5.png)
There is a tool window to look, search and go to any binding in your solution:
![Dpdt Extension Image 6](extension6.png)
You can enter a text to search everywhere, or use the following predicates to set a search scope:
- `from:` to search only in bind from fields
- `to:` to search only in bind to field
- `ca:` to search only in constructor arguments field
- `other` to search only in the scope field
- `location` to search only in binding location fieldKeep in mind: the searching is case sensitive (like C# too). Examples: `MyClass`, `ca:MyConstructorArgument`, `location:MyFile.cs` etc.
But the main function is to generate a binding clauses through the custom codelens. The following images makes the picture brighter:
![Dpdt Extension Image 0](extension0.png)
![Dpdt Extension Image 1](extension1.png)
![Dpdt Extension Image 2](extension2.png)
Settings window:
![Dpdt Extension Image 3](extension3.png)
`EnableWhitespaceNormalization` sets whitespace normalization when you create a new binding through Dpdt Visual Studio Extension.
A lot of thanks to bert2 and his amazing example `https://github.com/bert2/microscope`, without his `microscope` no Dpdt extension will appear because of lack of tutorials in the scope of VS extension development.
# Known problems
* Dpdt extension do not support few types (in different assemblies) with the same full name. I will investigate it further.
If any problem occurs with this extension or the generator itself, please let me know. I will need to see the following log files `C:\Users\\AppData\Local\Temp\dpdt_*.log`.
# Alternatives
You may be interesting in the following alternatives:
- [Strong Inject](https://github.com/YairHalberstadt/stronginject)
# Feedback
Any ideas for new features of Dpdt and Dpdt Visual Studio Extension are welcome.
Feel free to send a feedback by creating an issues here. Cheers!