18

I have a subclass of DbContext

public class MyContext : DbContext { }

and I have an IUnitOfWork abstraction around MyContext that implements IDisposable to ensure that references such as MyContext are disposed of at the appropriate time

public interface IUnitOfWork : IDisposable { }

public class UnitOfWork : IUnitOfWork 
{
    private readonly MyContext _context;

    public UnitOfWork()
    {
        _context = new MyContext();
    }

    ~UnitOfWork()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed;

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            if (_context != null) _context.Dispose();
        }

        _disposed = true;
    }
}

My UnitOfWork is registered with a lifetime scope of per (web) request. I have decorators of IUnitOfWork that could be registered as transient or lifetime scoped and my question is what should they do with regard to implementing IDisposable - specifically should they or should they not pass on the call to Dispose().

public class UnitOfWorkDecorator : IUnitOfWork
{
    private readonly IUnitOfWork _decorated;

    public UnitOfWorkDecorator(IUnitOfWork decorated)
    {
        _decorated = decorated;
    }

    public void Dispose()
    {
        //do we pass on the call?
        _decorated.Dispose();
    }
}    

I see 2 options (I'm guessing option 2 is the correct answer):

  1. It is expected that each Decorator will know whether it is transient or lifetime scoped. If a decorator is transient then it should not call Dispose() on the decorated instance. If it is lifetime scoped it should.
  2. Each decorator should only be concerned with disposing of itself and should never pass on the call to the decorated instance. The container will manage the call to Dispose() for each object in the call chain at the appropriate time. An object should only Dispose() of instances that it encapsulates and decorating is not encapsulation.
qujck
  • 14,388
  • 4
  • 45
  • 74
  • 2
    Excellent question. Aside, you should set _context variable to *null* in the Dispose method just after you have disposed that context. – Maarten Jul 30 '13 at 09:59
  • 1
    I agree with @Maarten: An excellent and very interesting question. – Steven Jul 30 '13 at 20:26

3 Answers3

15

what should [decorators] do with regard to implementing IDisposable

This comes back to the general principle of ownership. Ask yourself: "who owns that disposable type?". The answer to this question is: He who owns the type is responsible for disposing of it.

Since a disposable type is passed on to the decorator from the outside, the decorator didn't create that type and should normally not be responsible for cleaning it up. The decorator has no way of knowing whether the type should be disposed of (since it doesn't control its lifetime) and this is very clear in your case, since the decorator can be registered as transient, while the decoratee has a much longer lifetime. In your case your system will simply break if you dispose the decoratee from within the decorator.

So the decorator should never dispose the decoratee, simply because it doesn't own the decoratee. It's the responsibility of your Composition Root to dispose that decoratee. It doesn't matter that we're talking about decorators in this case; it still comes down to the general principle of ownership.

Each decorator should only be concerned with disposing of itself and should never pass on the call to the decorated instance.

Correct. The decorator should dispose everything it owns though, but since you're using dependency injection, it typically doesn't create much stuff itself and therefore doesn't own that stuff.

Your UnitOfWork on the other hand creates a new MyContext class and therefor has the ownership of that instance and it should dispose of it.

There are exceptions to this rule, but it still comes down to ownership. Sometimes you do pass on ownership of a type to others. When using a factory method for instance, by convention the factory method passes on the ownership of the created object to the caller. Sometimes ownership is passed on to a created object, such as .NET's StreamReader class does. The API documentation is clear about this, but since the design is such unintuitive, developers keep tripping over this behavior. Most of the types in the .NET framework don't work this way. For instance, the SqlCommand class doesn't dispose the SqlConnection, and it would be very annoying if it did dispose of the connection.

A different way of looking at this issue is from perspective of the SOLID principles. By letting the IUnitOfWork implement IDisposable you are violating the Dependency Inversion Principle, because "Abstractions should not depend on details; Details should depend on abstractions". By implementing IDisposable you are leaking implementation details into the IUnitOfWork interface. Implementing IDisposable means that the class has unmanaged resources that need disposal, such as file handles and connection strings. These are implementation details, because it can't hardly ever be the case that each implementation of such interface actually needs disposal at all. You just have to create one fake or mock implementation for your unit tests and you have proof of an implementation that doesn't need disposal.

So when you fix this DIP violation by removing the IDisposable interface from IUnitOfWork -and moving it to the implementation-, it becomes impossible for the decorator to dispose the decoratee, because it has no way of knowing whether or not the decoratee implements IDisposable. And this is good, because according to the DIP, the decorator shouldn't know -and- we already established that the decorator should not dispose the decoratee.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    You provide such a nice answer as always. I guess I am falling in love with you :). – crypted Jul 31 '13 at 06:52
  • Always nice to have fans :-) – Steven Jul 31 '13 at 06:57
  • @Steven so how do I ensure disposal of something such as `IDbConnection` (possible lame example but it is still an example) at the time the `UnitOfWork` goes out of scope? – qujck Jul 30 '14 at 23:50
  • 1
    @qujck: The `UnitOfWork` implementation can (and should) still implement `IDisposable`. The one who creates that instance can (and should) dispose it. But the `IUnitOfWork` interface should not leak these implementation details (according to the DIP). – Steven Jul 31 '14 at 06:20
  • Steven, what's your recommendation for when you cannot remove the `IDisposable` interface implementation from the interface being decorated? Say you are decorating a native interface from the framework, or some library, and that interface also implements `IDisposable`. Using Scrutor, I suspect one would _need_ to implement `Dispose` in the decorator and pass to the inner instance, as decorator registrations are overwritten on the container, and the only instance that will remain is the decorator one (so it would not be possible for the container to dispose the inner object). – julealgon Dec 04 '20 at 14:37
  • Your issue [seems specific](https://github.com/khellang/Scrutor/issues/91) to Scrutor, which is built on top of the very limited MS.DI implementation. I would not suggest letting the decorator dispose the decoratee, for all the reasons I've given above. Instead, either move the disposable logic out the decoratee component into a new dependency (which than implements `IDisposable`) -or- move to a mature DI Container. Most mature DI Containers have decent support for decorators and will certainly allow the decoratee to be disposed of out of the box. – Steven Dec 04 '20 at 15:59
7

Not an answer, but your UnitOfWork can be simplified a lot.

  • Since the class itself doesn't have any native resources, there's no need for it have a finalizer. The finalizer can therefore be removed.
  • The contract of the IDisposable interface states that it is valid for Dispose to be called multiple times. This should not result in an exception or any other observable behavior. You can therefore remove the _disposed flag and the if (_disposed) check.
  • The _context field will always be initialized when the constructor succeeds succesfully and Dispose can never be called when the constructor throws an exception. The if (_context != null) check is therefore redundant. Since DbContext can safely be disposed multiple times, there's no need to nullify it.
  • Implementing the Dispose Pattern (with the protected Dispose(bool) method) is only needed when the type is intended to be inherited. The pattern is especially useful for types that are part of a reusable framework, since there's no control over who inherits from that type. If you make this type sealed, you can safely remove the protected Dispose(bool) method and move its logic into the public Dispose() method.
  • Since the type does not contain a finalizer and can't be inherited, you can remove the call to GC.SuppressFinalize.

When following these steps, this is what's left of the UnitOfWork type:

public sealed class UnitOfWork : IUnitOfWork, IDisposable
{
    private readonly MyContext _context;

    public UnitOfWork()
    {
        _context = new MyContext();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

In case you move the creation of MyContext out of UnitOfWork by injecting it into UnitOfWork, you can even simplify UnitOfWork to the following:

public sealed class UnitOfWork : IUnitOfWork 
{
    private readonly MyContext _context;

    public UnitOfWork(MyContext context)
    {
        _context = context;
    }
}

Since UnitOfWork accepts a MyContext it doesn't have the ownership over, it is not allowed to dispose MyContext (since another consumer might still require its use, even after UnitOfWork goes out of scope). This means that UnitOfWork doesn't need to dispose anything and therefore doesn't need to implement IDisposable.

This of course means that we move the responsibility of disposing the MyContext up to 'someone else'. This 'someone' will typically be the same one that was in control over the creation and disposal of UnitOfWork as well. Typically this is the Composition Root.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • the template I was working to came from here http://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ – qujck Jul 30 '13 at 17:23
  • The articles pattern implementstion comes almost straight from the Framework Design Guidelines, but there are subtle diffeeences and some guidance has changed in the second edition. Although it is important to apply patterns, it is more important to understand them. The dispose pattern is designed for inheritance hierarchies. If you don't need such hierarchy, things become much easier. If you don't have native resources, things become easier as well. – Steven Jul 30 '13 at 19:58
  • Also take a look at the comments. There seems to be a lot of consensus with what I'm saying :-) – Steven Jul 30 '13 at 20:05
4

Personally, I suspect you need to handle this on a case-by-case basis. Some decorators might have good reasons to understand scoping; for most, it is probably a good default to simply pass it along. Very few should explicitly never dispose the chain - the main times I've seen that it was specifically to counteract a scenario where another decorator that should have considered scoping: didn't (always disposed).

As a related example - consider things like GZipStream - for most people, they are only dealing with one logical chunk - so defaulting to "dispose the stream" is fine; but this decision is available via a constructor overload which lets you tell it how to behave. In recent versions of C# with optional parameters, this could be done in a single constructor.

Option 2 is problematic, as it requires you (or the container) to keep track of all the intermediate objects; if your container does that conveniently, then fine - but also note that they must be disposed in the correct order (outer to inner). Because in a decorator chain, there may be pending operations - scheduled to be flushed downstream on request, or (as a last resort) during dispose.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    I hardly ever disagree with your comments, but this time I actually do. But I still need to give you +1, because after reading your answer I realized there's a bug in Simple Injector. S.I. disposes instances in the wrong order. As you said instance must be disposed from outer to inner, but that's not what happening in v2.3. – Steven Aug 20 '13 at 14:19
  • @Steven which bit did you disagree with, out of curiosity? – Marc Gravell Aug 20 '13 at 14:25
  • Did you read my answer? You give `GZipStream` as an example of a class that disposes the resource it gets from the outside, but this design goes against the basic framework design guidelines and is the opposite of how most implementations work .NET. Without a container you would use a series of `using` statements to dispose a nested graph, and very few implementation should actually dispose the chain, since this is behavior that users usually wont expect and disallows reuse of the out objects. The inner objects simply can't know if their parents should be reused or not. – Steven Aug 20 '13 at 14:46
  • @Steven the key point, as you make in your answer, is : when (if ever) do we consider ownership to have transferred. The fact is that some parts of the BCL *do* make assumptions about this, which is what I was trying to highlight. – Marc Gravell Aug 20 '13 at 14:58
  • Indeed. Sometimes we do (need to) transfer ownership, but IMO these should be rare. Even in the Framework Design Guidelines book there is a discussion about this pattern between the commenters (although Brad Abrams does describe this 'wrap to own' pattern [here](http://blogs.msdn.com/b/brada/archive/2003/10/06/50491.aspx)). Still, this deviation from the norm and I personally keep tripping over this 'design quirk'. The BCL team actually made the tradeoff between making those types really easy to use right in their common use case -and- confusing more experienced developers. – Steven Aug 20 '13 at 15:23