views:

391

answers:

3

I'm currently learning how to use Autofac, and I'm stuck with disposing IDisposable objects deterministically. Let me first present the situation before I'll state my problem.

Starting position:

Let's say my object model is defined through the following interfaces:

interface IApple : IDisposable
{
    void Consume();
}

interface IHorse
{
    void Eat(IApple apple);   // is supposed to call apple.Consume()
}

interface IHorseKeeper
{
    void FeedHorse();   // is supposed to call horse.Eat(apple)
                        //   where 'horse' is injected into IHorseKeeper
                        //   and 'apple' is generated by IHorseKeeper on-the-fly
}

Further, I define a delegate that will be used as an IApple factory:

delegate IApple AppleFactory;

Autofac configuration:

Now, I would register the above types as follows -- note that I'm omitting the code of both classes Apple and Horse, since they're trivial to implement:

var builder = new Autofac.ContainerBuilder();

builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Horse>().As<IHorse>();
builder.RegisterType<HorseKeeper>().As<IHorseKeeper>();
builder.RegisterGeneratedFactory<AppleFactory>();

My problem:

I don't quite know how to implement method IHorseKeeper.Feed. Here's what I currently have:

class HorseKeeper : IHorseKeeper
{
    private readonly IHorse horse;
    private readonly AppleFactory appleFactory;

    public HorseKeeper(IHorse horse, AppleFactory appleFactory)
    //                 ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^
    //                         constructor injection
    {
        this.horse = horse;
        this.appleFactory = appleFactory;
    }

    public void FeedHorse()
    {
        using (var apple = appleFactory())
        {
            horse.Eat(apple);
        }  // <- Dispose() apple now (ASAP), as it's no longer needed!
    }
}

This is the kind of code I would like to have, as it's completely Autofac-agnostic. It could just as well work with another IoC container, as long as AppleFactory works as expected.

However, because Autofac handles the AppleFactory for me, it will keep track of all IApple objects it produces for me, and will therefore want to Dispose them itself at the end of the container's lifetime. Ie., the produced apple will be disposed twice.

I suppose registering IApple as .ExternallyOwned() is no viable solution, as there might be cases where it's easier to let Autofac handle the IApples' lifetime.

Deterministic disposal with Autofac requires the creation of a nested container using container.BeginLifetimeScope(), however I don't want to use this inside HorseKeeper.FeedHorse because then HorseKeeper becomes dependent on Autofac, and I would like to keep my code IoC-agnostic.

Question:

How do I implement HorseKeeper.FeedHorse in a IoC (Autofac)-agnostic way while ensuring that on-the-fly generated objects are disposed properly?

+3  A: 

The only way is to modify the Apple registration with the ExternallyOwned modifier. This instructs Autofac to not track the object for disposal, but rather let someone external (your code) handle the disposal. But as you state, you will now have to make sure that all instances of Apple are disposed manually, since you will get no automatic help from Autofac.

builder.RegisterType<Apple>().As<IApple>().ExternallyOwned();

With this registration your Feed code will work as expected, though.

Note: on the discussion whether the interface should inherit IDisposable or not: IMO, when an interface inherits IDisposable, this is an indication to the "consuming" developer that the instance should be disposed at some point in time. In the case of IApple, since that interface is also IDisposable, the developer should make sure to dispose instances (and must also then be registered as ExternallyOwned). On the other hand, if the Apple class looked like this:

class Apple: IApple, IDisposable
{ }

consumers of the IApple is now fully unaware of the fact that instances is IDisposable. In this case we'll let the container handle disposal.

So my conclusion is that it is up to me as the developer of Apple and IApple to choose whether I'll require consumers to handle disposal or leave it up to a container.

Peter Lillevold
Thank you for this very clear and useful explanation, especially about how `ExternallyOwned` can work together nicely with `IDisposable` towards a consistent design. I'm still a little disappointed though that this seems to be an all-or-nothing situation, ie. that there seems to be no reasonable way to tell Autofac to manage disposal only for certain instances.
stakx
Not sure I understand clearly what you mean with "certain instances". LifetimeScope is a way of having more finegrained control over disposal in the container but I can see how this exposes the container to otherwise unaware classes. Otherwise I cannot see how Autofac possibly can know when instances should be disposed or not, without any input from external code.
Peter Lillevold
With "certain instances", I basically meant what @wcoenen demonstrated in his answer -- namely, registering the same concrete type twice, but for different interfaces, where one is marked as `ExternallyOwned` and the other isn't. A class can then choose from these versions and thereby remain largely IoC-agnostic (I think).
stakx
Owned<T> is the clue to Autofac that you're looking for. ExternallyOwned only applies to Apple, so if Apple has dependencies that require disposal this option will unfortunately "leak".
Nicholas Blumhardt
+2  A: 

If you sometimes want to manage the lifetime of Apple instances yourself, and sometimes let the container handle it, then you can define two interfaces:

public IApple
{
   void Consume();
}

public IDisposableApple : IApple, IDisposable
{
}

And then register the class twice:

builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Apple>().As<IDisosableApple>().ExternallyOwned(); 

You can then inject a DisposableAppleFactory into classes that need to create and dispose apples.

For classes which just need an apple with the same lifetime as the container, you inject IApple instead.

However, the fact that you need both may indicate that you are mixing newables and injectables. Apple may simply be a "newable" object, i.e. one that doesn't need to be managed by the IoC container.

Wim Coenen
You're a genious, sir! This is what I was after. You're correct that my example code may shown signs of sub-optimal design. I'll eventually get to that, too.
stakx
This works given the object model above, but unfortunately as with the other suggestion it will "leak" if Apple itself has any dependencies that also require disposal.
Nicholas Blumhardt
@Nicholas: agreed.
Wim Coenen
+6  A: 

Hi,

The other answers here are insightful, but have a problem. In both cases, if Apple has other dependencies that need disposal, correct cleanup won't happen.

Autofac 2 provides a new feature to help here, called "owned instances". I noticed that your registration code is Autofac 1.4, so if you're unable to upgrade let me know (there are other, less transparent, ways to do this.)

Register Apple as usual (not externally owned):

builder.RegisterType<Apple>().As<IApple>();

Declare AppleFactory as:

public delegate Owned<IApple> AppleFactory();

In Autofac 2, you do not need to call RegisterGeneratedFactory() anymore - this is automatic.

Then, in HorseKeeper, feed the horse like this:

public void FeedHorse()
{
    using (var apple = appleFactory())
    {
        horse.Eat(apple.Value);
    }
}

(Note the .Value property to get the underlying IApple.

At the end of the using block the apple, plus all of its dependencies, will be cleaned up.

Any other components that use IApple directly (as dependencies) will get the usual behaviour.

Cheers,

Nick

Nicholas Blumhardt
I like the semantics of `Owned<T>` even better than having a type registered as both `T` and `OwnedT` (as suggested by @wcoenen). However it necessitates a change to the application code, which IMHO ought to stay untouched. -- I don't understand one detail in your answer: You say that with the other answers here, clean-up will not work correctly if `IApple` has its own dependencies. Now, does this mean that Autofac will still manage a class's injected dependencies even if it is marked as `ExternallyOwned`? Is `ExternallyOwned` not "inherited" to dependencies?
stakx
+1 this is much like the experimental MEF `PartCreator` which returns a `PartLifetimeContext`. @stakx: "inheriting" `ExternalOwned` wouldn't work - when and by whom would the dependencies of the dependencies get disposed? `Apple` can't know about those.
Wim Coenen
@wcoenen: Of course you're right; `Apple` can know about its immediate dependencies but doesn't know about anything beyond them. @Nick: I appreciate your valuable input. I'm going to try out Autofac 2. `Owned<T>` is a nice feature, even if it requires changes to the code. @wcoenen again: I hope you don't mind if I move the checkmark from your answer over to Nick's, as his is technically the most sound.
stakx
Ah, yes, really like the simplicity of Owned<T>
Peter Lillevold