DI is no worse
The world before DI. First, let's consider the world before DI and before IoC. To get an instance of an object, the caller would have to do something like this:
var instance = new MyClass();
In this case, it is the caller that determines the lifespan of the object, not the implementation of the object itself. The caller has to decide whether to store the instance in a local variable, member field, or static field, or in some other holding area like HttpContext.Item
.
So the same problem existed before DI.
But... implementation could still control instancing. If you wanted the implementation to control the instancing of its objects, you could write a static factory, that may or may not always return the same instance. If you wanted "same instance" behavior, you'd actually implement a singleton (remember those?):
class MyClass
{
static MyClass _instance = new MyClass();
public MyClass GetInstance() => _instance;
}
Fast forward to the DI world. It's no different. In most cases, it's the caller that determines the rules for instantiation. After all, it's the caller that has to decide where to store the reference (even when the caller is an IoC container). So we have not made anything worse.
And the implementation can still control instancing. If you happen to have a class that absolutely requires a particular type of instancing behavior, you can always implement and inject a factory that controls the instancing. For example, if you absolutely need singleton behavior, you could write this:
class MyClassFactory : IMyClassFactory
{
static MyClass _instance = new MyClass();
public MyClass GetInstance() => _instance;
}
The caller can then register this factory any way they want and you will still end up with one and only one instance of MyClass.
//Startup
container.RegisterInstance<IMyClassFactory,MyClassFactory>();
//Some object in your program
class Foo
{
protected readonly MyClass _instance;
public Foo(IMyClasssFactory factory)
{
_instance = factory.GetInstance();
}
}
This way the implementation controls the instantiation rules via the factory, just as before DI. And this fact is made obvious to the caller, and impossible to ignore.