IOC containers and IDisposable

2020-02-05 08:13发布

问题:

It was recommended to me that, when using an IOC container, I should change this:

class Foobar: IFoobar, IDisposable {};

Into this:

interface IFoobar: IDisposable{};
class Foobar : IFoobar{};

I'm wondering if this is ok, or if it solves one problem and creates another. It certainly solves the problem where I badly want to do this:

using( IFoobar = myContainer.Resolve<IFoobar>() )
{ ... }

And now I know that any substitute won't cause a run-time error.

On the other hand, now all my mock objects must handle IDisposable too. Am I right that most any mocking framework handles this easily? If yes, then perhaps this is a non-issue.

Or is it? Is there another hidden gotcha I should watch for? It certainly occurs to me that if I were using an IOC container not for unit tests / mocking, but for true service independence, then this might be a problem because perhaps only one of my swappable services actually deals with unmanaged resources (and now I'm having to implement empty "IDispose" operations in these other services).

Even this latter issue I suppose I could live with, for the sake of gaining the ability to employ the "using" statement as I demoed above. But am I following a popular convention, or am I missing an entirely different and better solution?

回答1:

Deriving an interface from IDisposable is in my opinion a design smell that indicates a Leaky Abstraction. As Nicholas Blumhardt put it:

an interface [...] generally shouldn't be disposable. There's no way for the one defining an interface to foresee all possible implementations of it - you can always come up with a disposable implementation of practically any interface.

Consider why you want to add IDisposable to your interface. It's probably because you have a particular implementation in mind. Hence, the implementation leaks into the abstraction.

An DI Container worth its salt should know when it creates an instance of a disposable type. When you subsequently ask the container to release an object graph, it should automatically dispose the disposable components (if their time is up according to their lifestyles).

I know that at least Castle Windsor and Autofac does this.

So in your case, you should keep your type like this:

class Foobar: IFoobar, IDisposable {};

You may find Nicholas Blumhardt's post The Relationship Zoo interesting as well - particularly the discussion about Owned<T>.