DevTeam / Pure.DI

Pure DI for .NET
MIT License
506 stars 22 forks source link

Come up with an API that will allow injecting values in bindings based on lambda functions using local variables #61

Open NikolayPianikov opened 4 months ago

NikolayPianikov commented 4 months ago

Idea suggested by @ekalchev here.

NikolayPianikov commented 4 months ago

The API should be based on this interface, so that it would be possible to distinguish calls from custom lambda function logic belonging to the binding.

ekalchev commented 4 months ago

My idea comes from the usage of Unity DI. There is a concept of constructor override and dependency override.

https://www.tutorialsteacher.com/ioc/overrides-in-unity

The idea is to replace a specific constructor parameter for which you don’t have a registration, or you have a registration but in this particular resolve, you want to use a specific value for a type that is different from the registered one.

DependencyOverride works not only for the class you currently resolve but also for all dependencies down the dependency tree that have that dependency type in their constructors. As the name suggests, it overrides all dependencies down the dependency tree.

I can guess that the latter will be complex to implement.

ParameterOverride should be easy to implement—it overrides just a single parameter from the list of parameters of the constructor by matching the parameter name. With reflection-based DI, that approach was very fragile, and I was cautious to use it because if someone renames the parameter, you lose your override and you don’t get any errors indicating the problem. With a source generator, this can be made very durable and have a compile-time error if ParameterOverride doesn’t match a constructor parameter name or type.

Having this, you can pass a specific constructor parameter without the need to specify the others, as they can be picked up from known registrations. This is very convenient because when you add more parameters to the constructor, you won’t need to update the registration code if you already have this type registered.

NikolayPianikov commented 3 months ago

@ekalchev could you please review the approach in the examples Tag on injection site and Tag on injection site with wildcards. This approach works for all dependencies. At the moment it is difficult to do local overriding of bindings inside a factory - too many variants and complex graph.

ekalchev commented 3 months ago
DI.Setup(nameof(Composition))
    .Bind(Tag.On("*Service:dependency1", "*Consumer:myDep"))
        .To<AbcDependency>()
    .Bind(Tag.On("*Service:dependency2", "*Service:Dependency3"))
        .To<XyzDependency>()
    .Bind().To<Consumer<TT>>()
    .Bind<IService>().To<Service>()

Isn't it better to annotate somehow those registration with dependency override syntax

.Bind().To<Consumer<TT>>()
.Bind<IService>().To<Service>()

instead of annotating the constructor argument types?

.Bind<IDependency>("mydep").To<XyzDependency>()
.Bind().To<Consumer<TT>>().DependencyOverride<IDependency>("mydep")

if you want to override constructor parameter by name

.Bind<IDependency>("mydep1").To<XyzDependency>()
.Bind<IDependency>("mydep2").To<AbcDependency>()
.Bind().To<Service>()
     .ConstructorParameterOverride("dependency1", "mydep1")
     .ConstructorParameterOverride("dependency2", "mydep2")

I believe this syntax is easy to understand and when you inspect an type registration you immediately can spot overrides to a specific type or constructor argument.