I'm a little bit confused about Dispose()
methods in IDisposable
implementations with Autofac usage
Say I have a certain depth to my objects:
Controller
depends on IManager
;Manager
depends on IRepository
;Repository
depends on ISession
;ISession
is IDisposable
.This leads to the following object graph:
new Controller( new Manager( new Repository( new Session())));
Do I need to make my Manager and Repository implement IDisposable as well and call Manager.Dispose() in Controller, Repository.Dispose() in Manager, etc. or will Autofac automagically know which objects in my call stack properly need to be disposed? Controller object is already IDisposable as it derives from base ASP.NET Web API controller
To implement the IoC, you have the choice of two main patterns: Service Locator and Dependency Injection. The Service Locator allows you to "resolve" a dependency within a class and the Dependency Injection allows you to "inject" a dependency from outside the class.
If you access unmanaged resources (e.g. files, database connections etc.) in a class, you should implement IDisposable and overwrite the Dispose method to allow you to control when the memory is freed.
In object-oriented programming (OOP) software design, dependency injection (DI) is the process of supplying a resource that a given piece of code requires. The required resource, which is often a component of the application itself, is called a dependency.
IDisposable is an interface that contains a single method, Dispose(), for releasing unmanaged resources, like files, streams, database connections and so on.
The general rule of resources is that:
He who owns the resource is responsible of disposing of it.
This means that if a class owns a resource, it should either dispose of it in the same method that it created it in (in which case the disposable is called an ephemeral disposable), or if that’s not possible, this generally means that the owning class must implement IDisposable
, so it can dispose of the resource in its Dispose
method.
But it is important to note that, in general, a class should only own a resource if it is responsible of its creation. But when a resource is injected, this means that this resource already existed before the consumer did. The consumer didn't create the resource and should in that case not dispose of it. Although you can pass on the ownership of the resource to the consumer (and communicate this in the class’s documentation that ownership is passed on), in general you should not pass on the ownership, because this complicates your code and makes the application very fragile.
Although the strategy of transferring ownership of objects might make sense in some cases, for instance for types that are part of a reusable API (like System.IO.StreamReader
), it is always bad when dealing with components that are part of your object graph (our so called injectables). I’ll explain why below.
So even if your Controller
depends on dependencies that need to be disposed of, your controller should not dispose of them:
ObjectDisposedException
to be thrown.IDisposable
on that abstraction. This means this abstraction is leaking implementation details–that’s a violation of the Dependency Inversion Principle. You are leaking implementation details when implementing IDisposable
on an abstraction, because it is very unlikely that every implementation of that abstraction needs deterministic disposal and so you defined the abstraction with a certain implementation in mind. The consumer should not have to know anything about the implementation, whether it needs deterministic disposal or not.IDisposable
also causes you to violate the Interface Segregation Principle, because the abstraction now contains an extra method (i.e. Dispose
), that not all consumers need to call. They might not need to call it, because –as I already mentioned– the resource might outlive the consumer. Letting it implement IDisposable
in that case is dangerous, because anyone can call Dispose
on it causing the application to break. If you are more strict about testing, this also means you will have to test a consumer for not calling the Dispose
method. This will cause extra test code. This is code that needs to be written and maintained.So instead, you should only let the implementation implement IDisposable
. This frees any consumer of the abstraction from the doubts whether it should or shouldn't call Dispose
(because there is no Dispose
method to call on the abstraction).
Because only the implementation implements IDisposable
and only your Composition Root creates this implementation, it is the Composition Root that is responsible of its disposal. In case your DI container creates this resource, it should also dispose it. DI Containers like Autofac will actually do this for you. You can easily verify this. In case you wire your object graphs without the use of a DI Container (a.k.a. Pure DI), it means that you will have to dispose of those objects in your Composition Root yourself.
Considering the object graph given in your question, a simplistic code example that demonstrates both resolve (i.e. composing) and release (i.e. dispose) would like like this:
// Create disposable component and hold reference to it var session = new Session(); // create the complete object graph including the disposable var controller = new Controller( new Manager( new Repository( session))); // use the object graph controller.TellYoMamaJoke(); // Clean up resources session.Dispose();
Of course this example ignores complicating factors such as implementing deterministic cleanup, integration with application frameworks, and the use of DI Containers, but hopefully this code helps painting a mental model.
Note that this design change makes your code simpler. Implementing IDisposable
on the abstraction and letting consumers dispose of their dependencies will cause IDisposable
to spread out through your system like a virus and pollutes your code base. It spreads out, because for any abstraction, you can always think of an implementation that needs to clean up its resources, so you will have to implement IDisposable
on every abstraction. This means that every implementation that takes one or more dependencies will also has to implement IDisposable
as well, and this cascades up the object graph. This adds a lot of code and unnecessary complexity to each class in your system.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With