-
Notifications
You must be signed in to change notification settings - Fork 3
Home
Welcome to the DpdtInject wiki!
Dpdt is a 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.
It's only a proof-of-concept. Nor alpha, neither beta.
- Easy-to-read syntax
Bind<IA>().To<A>().WithTransientScope()
. - Generic
Get<T>
and non genericGet(Type t)
resolution. - Single object
Get
or collectionGetAll
resolution. -
GetFunc
resolution (theseFunc
objects are always singleton). - Custom constructor arguments
... Configure(new ConstructorArgument("message", Message))
. - Transient, singleton and constant (in progress) scopes.
- Additional compile-time safety
- At last, it's very, very fast.
More to come!
Because of source generators are generating new code based on your code, it's impossible to direclty debug your Module 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:
public partial class MyModule : DpdtModule
{
public override void Load()
{
Bind<IA, IA2>()
.To<A>()
.WithSingletonScope()
.When(cc =>
{
//here debugger is NOT working
return Debugger.Debug(cc);
})
;
}
}
public static class Debugger
{
public static bool Debug(IResolutionContext rc)
{
//here debugger is working
return true;
}
}
Examples of allowed syntaxes are available in the test project. Please refer that code.
Constructor is chosen at the compilation stage based on 2 principles:
- Constructors are filtered by
ConstructorArgument
filter. If noConstructorArgument
has defined, all existing constructors will be taken. - The constructor with the minimum number of parameters is selected to make binding.
Bind clause with no defined scope raises a question: an author did 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.
The only one instance of defined type is created. If instance is IDisposable
then Dispose
method will be invoked at the moment the module are disposing.
Each resolution call results with new instance. Dispose
for targets will not be invoked.
(in progress)
Constant scope is a scope when the module receive an outside-created object. Its Dispose
will not be invoked, because the module was not a parent of the constant object.
Each bind clause may have an additional filter e.g.
Bind<IA>()
.To<A>()
.WithSingletonScope()
.When(IResolutionContext rc =>
{
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 dependency tree root as possible.
Dpdt adds a warning to compilation log with the information about how many modules being processed. It's an useful bit of information for debugging purposes.
Dpdt will break ongoing compilation if binding has useless ConstructorArgument
clause (no constructor with this parameter exists).
Dpdt can detect cases of singleton takes a transient as its dependency, and make signals to the programmer. It's not always a bug, but warning might be useful.
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.
The life cycle of a module begins by creating it with new
. The module instance should only be 1 for the entire domain, since the Dpdt logic relies heavily on the generated private static readonly
fields. Attempting to create a second instance of the module will throw an error.
After that, the module is available for dependency resolutions.
The end of the life cycle of a module 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 module and use it for resolving in parallel . It is forbidden to resolve after a Dispose
.
- Because of optimizatoin, it's impossible to
Unbind
. - Because of source generators, it's impossible to direclty debug your bind code, including its
When
predicates. - Because of massive rewriting of
DpdtModule.Load
method, it's impossible to use a local variables (local methods and other local stuff) inConstructorArgument
andWhen
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. - No deferred bindings by design.
To keep Dpdt core as fast as I can, I choose move all facultative code from the core to the beautifier. For example the following things were moved:
-
IEnumerable<object>
inGetAll(Type requestedType)
method; Beautifier can returnList
and its main interfaces. -
Get<NotBindedType>()
results in unclearInvalidCastException
; Beautifier catch it and rethrow a appropriateNoBindingAvailable
signal.
I recommend use beautifier for resolutions unless you need ABSOLUTELY EVERY CPU tact.