I just stumbled upon a dependency injection problem that has not occured to me before. The concept of DI and it's advantages seem very clear to me. I use constructor injection in my project. So the DI container creates the classes for the interfaces needed by the constructor and I can use them without thinking about which exact implementation stands behind it. If the class implements IDisposable it is disposed by the container when it goes out of scope (Registered as type) or when the container itself is disposed (Registered as singleton). The class that uses the instance of this interface does not need to know. Love it. Great!
But as soon as I inject a factory like this and use it these assumptions do not hold anymore.
public class SomeClass(Func<ISomeDisposeableInterface> factory)
{
var x = factory();
//or even
using (var x = factory())
{...}
}
Dispose is not called when the instance runs out of scope. I mean of course it does not. The DI container can not know what I am doing with the created instances. And if I call dispose() on a instance created by this factory or put it in a using block I now have to know if it was registered as a singleton or there will be exceptions. This seems at odds with the whole concept.
Questions:
Kind regards!
Did I understand something wrong with the DI concept?
No, but it is more about DI implementation than a concept itself. In case of build-in DI container it will only dispose the of types it created (see the Disposal of services section of the doc). So the need to dispose is depended on how factory is implemented. Before introduction of Keyed DI services in .NET 8 in the default DI container quite common pattern was something like the following:
services.AddScoped<Func<SomeKeyType, ISomeDisposeableInterface>>(sp => key => key switch
{
key1 => sp.GetRequiredService<SomeImpl1>(),
key2 => sp.GetRequiredService<SomeImpl2>(),
});
Which obviously does not require manual disposal of the dependency.
Is using such a factory considered a anti pattern?
It depends. For example EF Core uses the same pattern with DbContext factory (IDbContextFactory
) - DbContext
instances created by the factory are not managed by the application's service provider and therefore must be disposed by the application.
Is there a other way to generate new instances of "SomeDisposeableInterface" inside the class that does not collide with the DI concept?
In general - yes. Something like the code snippet from earlier:
services.Add{Lifetime}<ISomeDisposeableInterface, SomeImpl>();
services.Add{Lifetime}<Func<ISomeDisposeableInterface>>(sp => () => sp.GetRequiredService<ISomeDisposeableInterface>());
But in practice it hugely depends on how the factory/service is registered and used.
And again - this is actually implementation detail. Some other DI container (see Default service container replacement doc) in theory can behave differently (though in practice I doubt that there is any which will intercept such things) or allow to hook up to the scope disposal for example (I have done something like this with Autofac).