'With DI, create a new instance every time the interface is used
I have something like the code below.
The inyected class has a state, so I want to create a clean new instance of the class implementing INumberWriter every time it is called. Is there a way to do this?
public class NumberWriter() : INumberWriter{
private int i = 1;
public void Write(){
ConsoleWrite(i++);
}
}
public class SomeClass(){
private INumberWriter writer;
public SomeClass(INumberWriter writer){
this.writer= writer;
}
public void AMethod(){
for(int i=0;i<5;i++){
writer.Write();// I want to see on console 11111 insted of 12345
}
}
}
Solution 1:[1]
This is not something that your DI Container can (or arguably should) solve for you. It's the DI Container's job to inject a dependency into a consumer. After that, the consumer stores the dependency in a private field, which means that -as long as the consumer lives- it will reuse that same dependency.
Fortunately, you are programming to interfaces, which means you can solve this problem using a special Proxy implementation that can be placed inside your application's Composition Root. For instance:
public class AlwaysNewNumberWriter : INumberWriter
{
public void Write() => new NumberWriter().Write();
}
Instead of registering NumberWriter in the container, you can now register AlwaysNewNumberWriter as follows:
services.AddSingleton<INumberWriter, AlwaysNewNumberWriter>();
This solution, however, might be too simplistic, because NumberWriter might have dependencies of its own, that need to be resolved from the DI Container. In that case, you'll need a slightly more complex solution:
public class DispatchingNumberWriter : INumberWriter
where TNumberWriter : INumberWriter
{
private readonly Func<INumberWriter> factory;
public DispatchingNumberWriter(Func<INumberWriter> factory)
{
this.factory = factory;
}
public void Write() => this.factory.Invoke().Write();
}
This DispatchingNumberWriter accepts a Func<INumberWriter> factory delegate, which it calls every time its Write method is called. This allows you to, instead of injecting the NumberWriter implementation into consumers, inject this DispatchingNumberWriter.
The following code demonstrates how to wire everything together:
services.AddTransient<NumberWriter>();
services.AddScoped<INumberWriter>(c =>
new DispatchingNumberWriter(
() => c.GetRequiredService<NumberWriter>()));
Please note the following about this last snippet:
- The
DispatchingNumberWriteris supplied with a delegate that calls back into the DI Container to resolve a newNumberWriter. This allows theNumberWriterto be created by the DI Container, which might construct the type using Auto-Wiring, i.e. automatically detecting and injecting its dependencies. - Because
NumberWriteris created by the DI Container, it is registered asTransient; otherwise instances might still be reused. - The
DispatchingNumberWriteris registered as Scoped, which the safest lifestyle for this class, because:- Registering
DispatchingNumberWriterasSingletonwill cause it'scparameter and the call to itsGetRequiredServicemethod to run in the container's global scope. This can cause issues in caseNumberWriteror one of its transient dependencies start implementingIDisposable(which could result in a memory leak) or in caseNumberWriterhas direct or indirect dependencies with theScopedlifestyle (which would make those dependenciesSingletons). - Registering
DispatchingNumberWriterasTransientwill have the desired effect except in case it gets (accidentally) directly or indirectly injected intoSingletonconsumer. Because in that case you'll get the same behavior as described in the previous point withDispatchingNumberWriterregistered asSingleton. Although the problem isn't different when injecting aScopedDispatchingNumberWriterinto aSingletonconsumer, MS.DI prevents the injection ofScopeddependencies intoSingletonconsumers, because it is known pitfall to prevent.
- Registering
Solution 2:[2]
When you need a new service on every iteration, you surely need to create a new one on every iteration. So there's no point in providing the dependency once upon creation of your SomeClass-instance, but upon every call to AMethod.
You may create some kind of factory to you class and let the factory create a new instance on each iteration:
public class WriterFactory
{
public INumberWriter => new NumberWriter();
}
public class SomeClass
{
private WriterFactory factory;
public SomeClass(WriterFactory factory){
this.factory = factory;
}
public void AMethod()
{
for(int i=0;i<5;i++)
{
var writer = this.factory().CreateNewWriter();
writer.Write();
}
}
}
Now instead of providing a writer to your class, you provide a factory to the class which itself creates the writers.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Steven |
| Solution 2 | MakePeaceGreatAgain |
