-
Notifications
You must be signed in to change notification settings - Fork 29
-
Consider the following code, the types A and B are like model types. Each of the model provide a specific implementation for generic interface IDependency.
The generic type Service consume the dependencies with a generic parameter.
public class A;
public class B;
public interface IDependency<T>
{
}
public class ADependency : IDependency<A>;
public class BDependency : IDependency<B>;
public interface IService<T>;
public class Service<T>(IDependency<T> _) : IService<T>;
public partial class Composition : ServiceProviderFactory<Composition>
{
[Conditional(nameof(DI))]
static void Setup() => DI.Setup()
.DependsOn(Base)
.Bind<IDependency<A>>().To<ADependency>()
.Bind<IDependency<B>>().To<BDependency>()
// what if i have much more of model types?
.RootBind<IService<A>>().To<Service<A>>()
.RootBind<IService<B>>().To<Service<B>>();
}
But the DI decleration for services in composition is really cumbersome if we have more model types.
Is there any way to simplify the decleration into one line?
.RootBind<IService<?>>().To<Service<?>>();
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 9 replies
-
There's no way to do that at the moment. This is due to the fact that we will have to analyze too many variants for the roots of the composition (hypothetically and generally). But you can simplify your setup like this:
public partial class Composition : ServiceProviderFactory<Composition> { [Conditional(nameof(DI))] static void Setup() => DI.Setup() .DependsOn(Base) .Bind().To<ADependency>() .Bind().To<BDependency>() .Bind().To<Service<TT>>() .Root<Service<A>>() .Root<Service<B>>(); }
Beta Was this translation helpful? Give feedback.
All reactions
-
I can overwite the new root method to record all injected types?
Beta Was this translation helpful? Give feedback.
All reactions
-
Unfortunately this only works for declared roots.
Beta Was this translation helpful? Give feedback.
All reactions
-
I think the optimal solution is this:
public partial class Composition : ServiceProviderFactory<Composition> { [Conditional(nameof(DI))] static void Setup() => DI.Setup() .DependsOn(Base) .Bind().To<ADependency>() .Bind().To<BDependency>() .Bind().To<Service<TT>>() .Root<Service<A>>() .Root<Service<B>>(); }
Wouldn't you like to define a composition root for each type of Service<T>?
Beta Was this translation helpful? Give feedback.
All reactions
-
I think the optimal solution is this:
public partial class Composition : ServiceProviderFactory<Composition> { [Conditional(nameof(DI))] static void Setup() => DI.Setup() .DependsOn(Base) .Bind().To<ADependency>() .Bind().To<BDependency>() .Bind().To<Service<TT>>() .Root<Service<A>>() .Root<Service<B>>(); }Wouldn't you like to define a composition root for each type of
Service<T>?
this a simple demo extracting from my production code. in production code:
- no default implementation for idependency or service, only interface and separated implementation for each model
- there're 30 or maybe more model types, really cumbersome to write those declarations and easy to miss some of them
- finally i really don't think duplicated code is good, it just make the code hard to maimaintain
i wonder whether the technique used in consumer-types example would help here, like build the service type for each model type
Beta Was this translation helpful? Give feedback.
All reactions
-
The difference with classic DI containers is that classic containers resolve dependencies dynamically at runtime. That is, they can potentially work with any set of types and allow late binding. There are disadvantages - runtime errors and performance degradation (reflection for dependency analysis).
Pure.DI - works differently. Already at compile time it should know the entire set of compositions it should be able to build for each specific root of the composition. It has its disadvantages - no late binding, no possibility to generate code to create all variants of object compositions - it is impossible. But it has its advantages - the code is as efficient as the code written manually in the pure DI paradigm, there are no runtime errors because all the analysis and construction of the composition code takes place at compile time.
In the pure DI paradigm, it is better if the composition has a single composition root and instead of the Resolve<T>() method uses method(s) (or property) to concrete compositions of objects (without any tricks).
ASP.NET Core and MS DI is done on late binding and universally uses GetService() to get object compositions. So for ASP.NET I would recommend using MS DI.
To use Pure.DI, you need to shift your focus a bit from using GetService() to using the roots of the composition. And then many of your questions will resolve themselves. For example, consider your example like this:
using System.Diagnostics; using System.Runtime.CompilerServices; using Pure.DI; using Pure.DI.MS; var composition = new Composition(); var app = composition.App; public class A; public class B; public interface IDependency<T>; public class ADependency : IDependency<A>; public class BDependency : IDependency<B>; public interface IService<T>; public class Service<T>(IDependency<T> _) : IService<T>; public class MyApp(IService<A> serviceA, IService<B> serviceB); public partial class Composition { private static void Setup() => DI.Setup() .Bind().To<ADependency>() .Bind().To<BDependency>() .Bind().To<Service<TT>>() .Root<MyApp>("App"); }
composition.App is the composition root here:
public MyApp App => new MyApp(new Service<A>(new ADependency()), new Service<B>(new BDependency()));
You have efficient code and simple setup.
In my solutions, I try to limit the number of roots to a minimum, like here.
If you want to add a later creation, you can do it like this:
public class MyApp(IService<A> serviceA, Func<IService<B>> serviceBFactory);
Beta Was this translation helpful? Give feedback.